home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Cream of the Crop 1
/
Cream of the Crop 1.iso
/
PROGRAM
/
LDB171.ARJ
/
LDB.DOC
< prev
next >
Wrap
Text File
|
1992-07-12
|
159KB
|
4,006 lines
LDB171 is shareware meaning try before you buy. If you use LDB171
for other than evaluation purposes you are required to register.
The registration fee is $60. Upon registration you will be sent a
hard copy manual and 3.5" DOS formatted diskette with the latest
revisions. Thank you for supporting the shareware concept!
The Loose Data Binder
version 1.71, 7-11-92
for AT&T C++ 2.x & 3.x
Copyright 1992
John W. Small
All rights reserved
PSW / Power SoftWare
P.O. Box 10072
McLean, Virginia 22102 8072
USA
Voice: (703) 759-3838
Software License Agreement
PSW / Power SoftWare licenses the bona fide purchaser of the
Loose Data Binder (LDB) to use the tool in the development of
software without royalty. The licensee may not publish any
portion of the tool kit and may only make backup copies of
software and documentation as a means of protecting the
licensee's investment against loss or damage.
A special consideration is made for LDB licensees desiring to
use LDB as a kernel of sorts within their commercially sold
software applications. In such cases the licensee may include
portions of the LDB tool source code without royalty for the
sole purpose of allowing the licensee's customers the
convenience of being able to recompile the licensee's product
in various computing environments adding what is known in
programming parlance as "portability" to the licensee's
product. All copyright notices in LDB source code must be
left unmodified. The licensee's software in whole or part
must not be merely a new wrapping or packaging of the LDB tool
capability. If such were the case a royalty bearing license
must be obtained. The licensee must notify customers in the
licensee's product license agreement of this arrangement and
furthermore advise them that their subsequent direct use of
LDB apart from recompiling the licensee's product requires
them to purchase separate copies (licenses) of LDB. The
requirement of notification can be satisfied by including the
following paragraph in the licensee's product license
agreement:
Please be advised that portions of this software include
LDB source code which is licensed from John W. Small,
copyright owner of LDB. Any subsequent direct use of LDB
source code apart from recompiling this product requires
you to purchase one or more copies of the LDB tool.
Special permission is hereby granted to the computer science
media (press) and its authors to publish any portion of the
header files comprising the LDB in reviews or articles. Thus
articles invoking the LDB interface may be published without
specific permission.
Likewise special permission is hereby granted to instructors
in public and private learning institutions to use LDB for
purposes of classroom instruction. Contact PSW for details on
LDB classroom materials.Limited Warranty
PSW / Power SoftWare warrants the Loose Data Binder toolkit to
be free from defects in materials and workmanship for a period
of 90 days from the original purchase date and will replace
same if found to be defective and reported in writing to PSW
within this period. The fitness of the Loose Data Binder
toolkit for any particular application is not implied nor
guaranteed. All other liabilities which may be construed are
specifically disclaimed except where and when required by law.
Registration
Please fill out and return the registration post card.
Notices of updates and new example diskettes are mailed from
the registration roster. Thank you!
Technical Support
Have a question, comment, or suggestion? I'm looking forward
to hearing from you. A comment post card has been included
for your convenience. If hard copy mail is too slow you can
contact me directly as indicated below.
John W. Small
Voice: (703) 759-3838
Acknowledgments
I wish to thank my Lord and Savior, Jesus Christ, who give
himself for me, so that I might receive God's gift of
reconciliation and eternal life.
Table of Contents
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 7
Chapter 1: Installation . . . . . . . . . . . . . . . . . . . 9
Chapter 2: Type Checking. . . . . . . . . . . . . . . . . . 11
Typeless Binders. . . . . . . . . . . . . . . . . . . . 11
Template Binders. . . . . . . . . . . . . . . . . . . . 12
Form "Templates". . . . . . . . . . . . . . . . . . . . 13
Form Cookbook . . . . . . . . . . . . . . . . . . . . . 14
Summary . . . . . . . . . . . . . . . . . . . . . . . . 15
Chapter 3: Hybrid Container . . . . . . . . . . . . . . . . 17
Elastic-Array Category. . . . . . . . . . . . . . . . . 17
Stack-Queue-Deque Category. . . . . . . . . . . . . . . 19
List Category . . . . . . . . . . . . . . . . . . . . . 20
Sort-Search-Unique Category . . . . . . . . . . . . . . 22
Summary . . . . . . . . . . . . . . . . . . . . . . . . 23
Chapter 4: Assigning, Cloning, and Deleting . . . . . . . . 25
Summary . . . . . . . . . . . . . . . . . . . . . . . . 29
Chapter 5: Persistence. . . . . . . . . . . . . . . . . . . 31
Summary . . . . . . . . . . . . . . . . . . . . . . . . 39
Chapter 6: Mutually Owned Containers and Nodes. . . . . . . 41
Summary . . . . . . . . . . . . . . . . . . . . . . . . 54
Chapter 7: Reference. . . . . . . . . . . . . . . . . . . . 55
Binder. . . . . . . . . . . . . . . . . . . . . . . . . 55
MBinder . . . . . . . . . . . . . . . . . . . . . . . . 87
Mutual. . . . . . . . . . . . . . . . . . . . . . . . . 92
StreamRegistry. . . . . . . . . . . . . . . . . . . . . 101
Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Introduction
The Loose Data Binder, LDB, is an unconventional persistent
container class having a hybrid stack, queue, priority queue,
deque, list, array interface and built-in sort, search, and iterate
functions in one flat class. Being persistent, an LDB can be saved
on a stream and later reloaded while multiple references to its
various elements are automatically resolved, thus allowing complex
container networks to persist without a second thought. I think
that you will find LDB a lot more efficient, convenient, and
comprehensible than conventional OOP container class libraries with
their towering, convoluted hierarchies. And LDB's unconventional
hybrid structure allows you to implement practical algorithms which
conventional "textbook" libraries can't touch!
Think of the LDB as an elastic array of void pointers pointing to
whatever data you wish to bind. Unlike a conventional C++ array
that is statically sized, the LDB can expand or contract
automatically to accommodate varying numbers of cells. You control
the granularity and hysteresis of this elasticity. And of course
you can impose strong type checking for worry free plug and play
containers for all your data types using templates or forms if your
compiler doesn't support templates.
When you use LDB's for all your linked list and container needs,
your applications will require less design effort, less
documentation, less testing, less code space, less programming
time, and less maintenance effort. And PSW's OEM friendly license
agreement is a real boon to commercial class designers.
Chapter 1
Installation
If you are a DOS user, insert the LDB distribution diskette in the
"A" drive and type A:\install <enter>. Otherwise, copy the
following files to the appropriate directories for the C++ compiler
and operating system you are using.
binder.hpp Binder class (basic LDB class)
binder.cpp
tbinder.hpp template
fbinder.hpp form for strong type checking
fbinder.cpp without templates
mutual.hpp mutually owned containers and nodes
mutual.cpp
fmutual.hpp form for deriving mutually owned
fmutual.cpp heterogeneous nodes
You may need to rename the extensions of these files to suit your
compiler. LDB source code is AT&T C++ version 2.x, 3.x compatible.
For those of you who don't have smart linker/librarians on your
system, the Loose Data Binder source is broken down into individual
member functions and grouped into appropriate files to facilitate
the building of libraries. These files can be found in the
"libsrc.cpp" subdirectory on the distribution diskette. You will
find a makefile in this subdirectory. Edit the macros in the
makefile to suit your environment. A LDB library can be quickly
built for your compiler using this makefile.
The examples found in the text are grouped together in the
"examples.cpp" subdirectory.
Be sure to read any readme.doc files that may appear on the
distribution diskette for last minute revisions.
Chapter 2
Type Checking
The Loose Data Binder's basic container class is named Binder. The
Binder class used by itself performs no type checking on the data
being bound. However, by using templates a Binder can be made to
impose strong type checking. Even if your compiler doesn't support
templates, strong type checking can still be achieved with forms.
Forms can also be used for data types not meeting the template
requirements.
Typeless Binders
Sometimes you may have a need for a typeless binder, e.g. binding
heterogenous data in the same container. A disadvantage of
typeless binders is that they can not be expected to be persistent
since nothing is known about the type of data bound within. The
following trivial program queues up five dynamically allocated
integers. Then the "list" is walked while displaying each node's
contents. When the Binder is destructed, its nodes are deleted.
Please note that any combination of data types could have been
queued into a typeless Binder, not just integers.
// examp201.cpp - link with binder.obj
#include "binder.hpp"
main()
{
Binder b(BDR_DDELETE,5);
for (int i = 0; b.insQ(new int(i)); i++);
while (++b)
cout << *(int *)b.get() << "\n";
return 0;
}
Template Binders
Suppose that we wanted to insure that only integers were bound in
the Binder. One way is to use C++ templates as follows:
// examp202.cpp - link with binder.obj
#include "tbinder.hpp"
TBINDER(int,IntBdr,IntBdR);
main()
{
IntBdr b(BDR_DDELETE|BDR_DNEW,5);
for (int i = 0; b.insQNew(&i); i++);
while (++b)
cout << *(int *)b << "\n";
return 0;
}
To create a strongly type checked "int" Binder we include the
tbinder.hpp file and invoke the TBINDER() macro to generate the
template instance. TBINDER()'s first parameter is the type of data
the container is to hold. The second is the name of the Binder
type that will perform the type checking, and the third is the name
of a pointer to this new Binder type.
Data types are required to have:
a default constructor,
a copy initializer constructor, i.e. int(int&),
a destructor if one is necessary,
an assignment operator, i.e. operator=(),
an overloaded stream insertion operator,
i.e. ostream& operator<<(ostream&,int&),
and a stream extraction operator,
i.e. istream& operator>>(istream&,int&)!
Built in types such as int, float, double, etc. implicitly fulfill
these requirements and can thus be readily used with templates.
Form "Templates"
Strong type checking can be achieved without templates by using
forms. Data types must meet the same template imposed requirements
to be used with forms in this manner.
// examp203.cpp - link with binder.obj
#define FType int
#define FBinder IntBdr
#define FBindeR IntBdR
#include "fbinder.hpp"
main()
{
IntBdr b(BDR_DDELETE|BDR_DNEW,5);
for (int i = 0; b.insQNew(&i); i++);
while (++b)
cout << *(int *)b << "\n";
return 0;
}
The tbinder.hpp file is no longer used. This time we define FType,
FBinder, FBindeR instead of invoking the TBINDER() macro. By
including the fbinder.hpp file after these macros, fbinder.hpp in
effect generates a "form" instance in a manner similar to a
template instance. The inclusion of fbinder.hpp leaves FType,
FBinder, and FBindeR undefined so that the procedure can be used
repeatedly to instantiate various Binder types.
Form Cookbook
You can also use forms to create strongly type checked Binders for
data types that do not have the prerequisites for templates or form
"templates." For sake of argument, suppose the "int" type did not
meet the requirements for templates or form "templates." Simply
copy fbinder.hpp to intbdr.hpp and fbinder.cpp to intbdr.cpp.
Globally replace FType with int, FBinder with IntBdr, and FBindeR
with IntBdR in both files. If "int" really didn't have the
prerequisites we would proceed to edit the intbdr.cpp file as
follows:
edit function: to perform:
Dassign() assignment
Dnew() copy-initialize construction
Ddelete() destruction
Dstore() stream insertion
Dload() stream extraction
With intbdr.hpp and intbdr.cpp now ready we can proceed with the
example:
// examp204.cpp - link with intbdr.obj and binder.obj
#include "intbdr.hpp"
main()
{
IntBdr b(BDR_DDELETE|BDR_DNEW,5);
for (int i = 0; b.insQNew(&i); i++);
while (++b)
cout << *(int *)b << "\n";
return 0;
}
Summary
Binders used by themselves preform no type checking and are not
persistent, but they are useful for holding heterogeneous data
types. Templates are used to create Binders with strong type
checking. If your compiler doesn't support templates, form
"templates" can be used in a manner similar to templates. Forms
can also be cloned and edited for use with data types that do not
have all the prerequisites for templates or form "templates", i.e.
default constructor, copy initializer constructor, destructor,
assignment operator, and overloaded stream insertion and extraction
operators. Remember, all strongly typed checked Binders can
persist!
Chapter 3
Hybrid Container
The next step to learning to program with the LDB tool is to
mentally categorize the multifaceted behavior of the Binder class.
Let's call the first category "elastic-array." The elastic-array
concept provides the foundation for the rest of the inherent
behaviors of a Binder. Let's call the second category "stack-
queue-deque", the third "list", and the last "sort-search-unique."
Remember, a Binder can exhibit any or all of these behaviors at any
time which is why it is referred to as an unconventional flat
container class.
Elastic-Array Category
Imagine a C++ array (vector) of void pointers that can be declared
to have any number of cells. You can read or write a pointer value
to any cell in this array. Okay, nothing spectacular so far. But
now take this same array and insert new cells or extract old ones
at any location on the fly and you have an elastic array. The
Binder class allows you to specify the granularity and hysteresis
of expansion and contraction. In other words, when a new cell
needs to be added how many additional spare cells should be
provided to optimize future expansion efforts? How many spare
cells should be allowed to accumulate before contraction? You can
specify this granularity and hysteresis with the delta parameter in
either a constructor call or housekeeping function. Add an
overloaded subscript operator and forEach iterator and you have a
handy little array class. The following list of functions catalogs
this elastic-array behavior:
vector() Limit() setLimit()
pack() Delta() setDelta()
Nodes() MaxNodes() setMaxNodes()
vacancy() vacancyNonElastic()
atIns() atInsNew() atRmv()
allRmv() atDel() atDelAsg()
allDel() atPut() atPutNew()
atPutAsg() atGet() operator[]()
atGetAsg() atXchg() index()
forEach()
Elastic array operation is really summed up by two primitives,
atIns() and atRmv(). AtIns() writes a pointer value to a newly
inserted cell in the Binder's logical array at the index indicated.
The cell originally at the index and all successive cells are
pushed back one index location, expanding the physical array if
necessary. AtRmv() performs the inverse function of atIns(),
extracting the indexed cell returning its pointer while pulling all
successive cell indices forward by one. The physical array is
shrunk automatically if excessive space has accumulated.
The following example shows a typeless Binder used to sort a vector
of strings. Notice the use of typecasts for function pointers as
well as for data pointers.
// examp301.cpp - link with binder.obj
#include <string.h>
#include "binder.hpp"
char *v[] = {
"Vectors can be",
"'exploded' into Binders",
"sorted and then",
"'imploded' back to vectors!",
0
};
main()
{
Binder b ((voiDV) v); // explode constructor
cout << "\n\nExploded, unsorted vector ...\n\n";
for (unsigned i = 0; b[i]; i++)
cout << (char *) b[i] << "\n";
b.sort ((BDRcomP) strcmp); // sort
char **sV = (char **) b.vector(); // implode
if (!sV) return 1;
cout << "\n\nImploded, sorted vector ... \n\n";
for (i = 0; sV[i]; i++)
cout << sV[i] << "\n";
delete sV;
return 0;
}
Stack-Queue-Deque Category
A stack provides LIFO (Last In, First Out) storage. The Binder has
a complete set of stack primitives. These functions don't disturb
list structure of the LDB, e.g. current node setting. A queue
provides FIFO (First In, First Out) storage. And the deque
provides FOLO (First Out, Last Out) storage. Think of the deque as
a combination stack-queue with the additional ability of being able
to pop the rear of the queue. The following list of Binder
functions support this stack-queue-deque structure:
push() pushNew() pop()
popDel() popDelAsg() top()
topAsg() insQ() insQNew()
unQ() unQDel() unQDelAsg()
rear() rearAsg() operator<<()
operator>>()
The following example shows a form Binder for floats being built
like a stack and unloaded as a deque. UnQDelAsg() copies from the
last node to the address given before deleting the node.
// examp302.cpp - link with binder.obj
#define FType float
#define FBinder fbdr
#define FBindeR fbdR
#include "fbinder.hpp"
main() // count to 5
{
fbdr fb(BDR_DASSIGN|BDR_DNEW|BDR_DDELETE,5);
for (float f = 1.0; fb.pushNew(&f); f++);
while (fb.unQDelAsg(&f))
cout << f << "\n";
return 0;
}
List Category
Any Binder can be treated as if it were a doubly linked list
thereby providing sequential storage. In a Binder's header, a
current node index is maintained that lets the list primitives know
where the insertion or removal is to take place. The elastic-array
and stack-queue-deque primitives don't disturb this current node
index unless of course it points to the cell that's being removed.
In that case the current node becomes undefined just like it was
when the Binder was first constructed. Here's a list of the list
primitives:
CurNode() setCurNode() ins()
insNew() rmv() del()
delAsg() put() putNew()
putAsg() get() getAsg()
next() operator++() nextAsg()
prev() operator--() prevAsg()
firstThat() lastThat()
The following example shows a trivial structure designed to be use
with templates or form "templates." It has default and copy
initializer constructors, destructor, assignment and overloaded
stream insertion and extraction operators. It is use here to
generate a Binder template instance.
// examp303.cpp - link with binder.obj
#define fsbfile "examp203.txt"
#include <iostream.h>
#include <iomanip.h>
struct FS {
float f;
FS(float f = 0.0) { this->f = f; }
FS(FS& fs) { f = fs.f; }
FS& operator=(FS& fs) { f = fs.f; return fs; }
~FS() {}
};
inline ostream& operator<<(ostream& os, FS& fs)
{ return os << fs.f; }
inline istream& operator>>(istream& is, FS& fs)
{ return is >> fs.f; }
#include "tbinder.hpp"
TBINDER(FS,FSbdr,FSbdR);
int fcmp(const FS *F1, const FS * F2)
{ return (F2->f - F1->f); }
main() // count to five
{
FSbdR FSB = new FSbdr (BDR_DNEW | BDR_DDELETE
| BDR_DSTORE,5);
if (!FSB) return 1;
FS fs = 1.0;
while (FSB->insNew(&fs)) fs.f++;
FSB->setComP ((BDRcomP) fcmp);
Binder::RegisterComP ((BDRcomP) fcmp);
FSB->save(fsbfile);
delete FSB;
FSbdr fsb(fsbfile);
fsb.sort();
fsb.setFlags(BDR_DASSIGN);
while (fsb.prevAsg(&fs))
cout << fs.f << endl;
return 0;
}
The first list is dynamically allocated and limited to five nodes
by its constructor. The ins()/insNew() functions make their
insertions after the current node making the newly inserted node
current. In the first iteration the current node is undefined so
the new node is simply placed at the end of the list and made the
current node. Note that the compare function is coded for
descending order! The Binder compare function is set to one that
compares float structures and gets saved on the stream. Compare
functions must be registered to be persistent! After the Binder is
reloaded the BDR_DASSIGN flag is set so that primitives with "Asg"
suffixes are enabled. This list is walked backwards after being
sorted in descending order, in order to count to five.
Sort-Search-Unique Category
Binders can also be sorted and searched on a supplied compare
function. These primitives affect the list's current node setting.
Sorted() unSort() setComP()
ComP() sort() insSort()
insSortNew() insUnique() insUniqueNew()
findFirst() findNext() findLast()
findPrev() findAll()
With these primitives you can build bags, sets, dictionaries, etc.
Our demo is a trivial exercise of the setComP(), insSort(), and
findAll() primitives. Pointers to the integers are inserted in
sorted order using the supplied intcmp() compare function.
FindAll() uses the same compare function to count matches.
// examp304.cpp - link with binder.obj
#include "binder.hpp"
int v[] = { 25, 3, 43, 17, 7, 35, 3 };
int intcmp(const int *I1, const int *I2)
{ return (*I1 - *I2); }
main()
{
Binder b;
b.setComP ((BDRcomP) intcmp);
for (int i = 0; i < sizeof(v)/sizeof(v[0]); i++)
b.insSort ((voiD) &v[i]);
i = 3;
cout << "\n" << b.findAll(&i) << " " << i
<< "'s are present in the following "
"sort Binder.\n\n";
while (b.next())
cout << *(int *)b.get() << "\n";
return 0;
}
Summary
The easiest way to conceptualize Binder operations is to classify
its primitive behaviors into four categories: elastic-array, stack-
queue-deque, list, and sort-search-unique. A Binder has an
inherent current node setting associated with its list behavior
which is also modified by its sort-search-unique primitives but
left undisturbed by the elastic-array and stack-queue-deque
primitives.
Chapter 4
Assigning, Cloning, and Deleting
In the last chapter you were exposed to Binder primitives with
"Asg", "New", and "Del" suffixes and "del" within their names.
These primitives perform on the fly assigning, cloning, and
deleting of your data types, respectively, but otherwise they work
essentially the same as their namesake counterparts. The
BDR_DASSIGN, BDR_DNEW, and BDR_DDELETE flags each have to be set to
enable their respective "Asg", "New", and "Del/del" functions.
Only strongly typed checked Binders know about the data types bound
within, so even though you may set these flags in typeless Binders,
their corresponding functions are left disabled except for the
"Del/del" primitives.
For example, take the next() and nextAsg() primitives. Next()
advances the list's current node by one returning a pointer to the
new current node. NextAsg() does the same thing, but it also
copies the node to the destination address given as a parameter.
// examp401.cpp - link with binder.obj
#include "tbinder.hpp"
TBINDER(int,IntBdr,IntBdR);
main()
{
IntBdr b (BDR_DASSIGN | BDR_DNEW | BDR_DDELETE, 5);
for (int i = 0; b.insQNew(&i); i++);
while (b.nextAsg(&i)) cout << i << "\n";
b.allDel();
return 0;
}
This example also shows the insQNew() cloning an integer and
inserting the pointer to the clone instead of to the original.
AllDel() is called to remove all nodes from the Binder though this
is really not necessary since the Binder destructor will call it automatically if BDR_DDELETE flag is set. If you set BDR_DNEW you
will most likely want to set BDR_DDELETE also. If you set
BDR_DDELETE and don't want the Binder's destructor to delete your
nodes, you could either call allRmv() or reset the BDR_DDELETE flag
prior to the destructor call. AllRmv() removes all cells from the
elastic array discarding the pointers without deleting them.
Binder "Asg", "New", and "Del/del" primitives invoke the
assignment, copy initializer, and destruct operations,
respectively, on the data type being stored within the Binder.
This is accomplished via the Binder's Dassign(), Dnew(), and
Ddelete() protected virtual functions. In a typeless Binder,
Dassign() and Dnew() do nothing since the data type is unknown.
Thus the "Asg" and "New" primitives are disabled. In a strongly
type checked Binder they are able to correctly invoke the proper
operations.
C++ strings can't be used as is with Binder templates or form
"templates" because they don't have a meaningful default
constructor or copy initializer. The following example
encapsulates a C++ string in a type that can be used with either a
Binder template or form "template."
// examp402.cpp - link with binder.obj
#include <string.h>
#include <iostream.h>
struct str {
char *s;
str(const char *cs = (char *)0) // default constructor
{ s = (cs? strdup(cs) : (char *)0); }
str(str& si) // copy initializer
{ s = (si.s? strdup(si.s) : si.s); }
void operator=(str& si) // assignment
{ delete s; s = (si.s? strdup(si.s) : si.s); }
void operator=(const char * cs)
{ delete s; s = (cs? strdup(cs) : (char *)0); }
~str() { delete s; } // destructor
};
ostream& operator<<(ostream& os, str&)
{ return os; }
istream& operator>>(istream& is, str&)
{ return is; }
#include "tbinder.hpp"
TBINDER(str,StrBdr,StrBdR);
main()
{
StrBdr sb(BDR_DDELETE);
sb.ins(new str("line one"));
str s("Why won't this string appear in the Binder?");
sb.insNew(&s);
s = "line two";
sb.setFlags(BDR_DNEW);
sb.insNew(&s);
sb.setCurNode(); // reset current node
while (sb.next()) cout << ((str *)sb)->s << "\n";
return 0;
}
The first insNew() gracefully fails because the BDR_DNEW flag is
not set. That's remedied before the second attempt. Both template
and form "template" Binder instances have the implicit type cast
operator defined as returning a pointer to the current node. The
type of the typecast is of course the type being bound within the
Binder, e.g. (str *).
We can gain more insight into the operation of the Dassign(),
Dnew(), and Ddelete() virtual functions by considering how we can
alternatively adapt a form Binder instance to accommodate C++
strings instead of the using the wrapper structure of the previous
example. We can't just use a template by invoking TBINDER() with
"char *" as its first parameter. Nor can we use form "templates"
for the same reason. The solution is to copy fbinder.hpp to
strbdr.hpp and fbinder.cpp to strbdr.cpp and globally replace FType
with char, FBinder with StrBdr, FBindeR with StrBdR in both files.
Strbdr.cpp is additionally edited to allow for assigning strings
via the Binder's overridden Dassign() virtual function. Dassign()
is used by all Binder primitives with the "Asg" suffix in their
names to assign data to or from its bound nodes and the outside
world. Our string assignment overwrites the destination string up
to its terminating '\0' or for the length of the source string,
which ever is shorter.
voiD StrBdr::Dassign(voiD D, const voiD S)
{
while (*(char *)D)
if ((*((char *)D)++ = *((char *)S)++ )
== '\0') break;
return D;
}
The Binder primitives will never call Dassign() unless the
BDR_DASSIGN flag is set. Also Dassign()'s parameters are
guaranteed never to be NULL! If Dassign() returns NULL the calling
Binder primitive is obliged to leave the Binder in an unchanged
state. For example, Binder::nextAsg() will leave the current node
setting untouched if your Dassign() returns NULL. Thus Binder
assignment operations can be inhibited by either failing to set the
BDR_DASSIGN flag or overriding Dassign() to always return NULL.
Please note that the strong type checking is performed by the
Binder's public primitives invoking Dassign() and the various other
protected virtual functions and not by the virtual functions
themselves. After all you can't override a virtual function unless
its prototype is identical to the original virtual function.
String cloning has been provided for by overriding the Binder's
Dnew() virtual function. Dnew() is used by all Binder primitives
with the "New" suffix in their names to clone data on the fly,
binding the clone instead of the original data as is usually the
case. Dnew() performs essentially what a copy initializer
constructor would, in our case it calls the standard C library
string function, strdup().
voiD StrBdr::Dnew(const voiD D)
{ return (voiD) strdup((char *)D); }
The Binder primitives will never call Dnew() unless the BDR_DNEW
flag is set. Again, Dnew()'s parameter is guaranteed never to be
NULL. If your Dnew() returns NULL the Binder is left in an
unaltered state. Thus Binder cloning operations can be inhibited
by either failing to set the BDR_DNEW flag or overriding Dnew() to
always return NULL.
The Binder's Ddelete() virtual function provides a combination
destruction - deallocation service for Binder primitives having
"del" or "Del" in their names. Ddelete() will never be called
unless the BDR_DDELETE flag is set and there is something to
delete, i.e. Ddelete()'s parameter is never NULL. Our Ddelete()
for strings simply deallocates memory.
void StrBdr::Ddelete(voiD D)
{ delete (char *) D; }
You can inhibit all Binder deletion operations by failing to set
the BDR_DDELETE flag. With strbdr.hpp and strbdr.cpp now ready we
can redo the last example as follows.
// examp403.cpp - link with strbdr.obj and binder.obj
#include <string.h>
#include "strbdr.hpp"
main()
{
StrBdr sb(BDR_DDELETE | BDR_DNEW);
sb.ins(strdup("line one"));
sb.insNew("line two");
sb.setCurNode();
while (sb.next()) cout << (char *)sb << "\n";
return 0;
}
Summary
The Binder "Asg", "New", and "Del/del" primitives perform on the
fly assignment, cloning, and deletion, respectively, on the data
elements of a strongly type checked Binder. These primitives are
enabled by setting the BDR_DASSIGN, BDR_DNEW, and BDR_DDELETE
flags. When these flags are set in a typeless Binder their
corresponding primitives remain disabled except for "Del/del"
primitives. By defining a form instance's Dassign() and Dnew()
virtual functions to return NULL the "Asg" and "New" primitives can
be permanently disabled regardless of the flags settings.
Chapter 5
Persistence
Persistence refers to the ability of a Binder to persistent beyond
the termination of the program in which it exists. In other words,
Binder objects can be made to persist between program invocations.
This view of a data object hides the details of the implementation
of its file record structure, encapsulating it within the object
where OOP rightly says it belongs.
You saw an example of a persistent float Binder in examp303.cpp
back in chapter 3. Since floats can be used directly with
templates or forms, that example was overkill. It was used, among
other things, to demonstrate how to make your struct or class
template-form aware. Let's rework that example making it simpler
but using forms this time. Recalling from before, please note that
the compare function is coded for descending order.
// examp501.cpp - link with binder.obj
// rework of examp303.cpp
#define ffile "examp501.txt"
#include <iostream.h>
#include <iomanip.h>
#define FType float
#define FBinder fbdr
#define FBindeR fbdR
#include "fbinder.hpp"
int fcmp(const float *f1, const float * f2)
{ return (*f2 - *f1); } // descending order
main() // count to five
{
fbdR fB = new fbdr (BDR_DNEW | BDR_DDELETE
| BDR_DSTORE,5);
if (!fB) return 1;
float f = 1.0;
while (fB->insNew(&f)) f++;
fB->setComP ((BDRcomP) fcmp);
Binder::RegisterComP ((BDRcomP) fcmp);
fB->save(ffile);
delete fB;
fbdr fb(ffile);
fb.sort();
fb.setFlags(BDR_DASSIGN);
while (fb.prevAsg(&f))
cout << f << endl;
return 0;
}
The first list is dynamically allocated and limited to five nodes
by its constructor. InsNew() inserts after the current node making
the newly inserted node current. In the first iteration the
current node is undefined so the new node is simply placed at the
end of the list and made the current node. The Binder compare
function is set to the float compare but not used except to be
stored on stream. Compare functions must always be registered in
order to be persistent. (Upon registration, they are assigned an
id which is what actually gets streamed. Thus compare functions
must always be registered in the same order to receive the same
id.) After the Binder is reloaded the BDR_DASSIGN flag is set so
that primitives with "Asg" suffixes are enabled. This second list
is walked backwards after being sorted in descending order in order
to count to five.
A Binder stores itself on a stream by invoking Dstore(), a
protected virtual function, for each of its elements. Each Binder
element is passed as the second parameter to Dstore(). Dstore()
can only be invoked when the BDR_DSTORE flag is set. The Binder's
default Dstore() does nothing. After all, a typeless Binder knows
nothing about the data types of its elements. But strongly type
checked Binders do since template-form instances override Dstore().
//template<class TYPE>
//form's FTYPE replaces TYPE
virtual void Dstore(ostream& os, voiD D)
{
os << *(TYPE *)D << BDRendm;
}
virtual voiD Dload(istream& is)
{
TYPE t;
is >> t >> BDRnextm;
return (voiD) new TYPE(t);
}
The Binder's Dload() takes care of loading elements back from a
stream. There is no BDR_DLOAD flag since loading from stream is a
constructor process. To set a load flag in a call to a load
constructor is ludicrous.
In chapter 4, examp402.cpp, we saw how a string wrapper structure
was used to appease a Binder's template requirements. Recall that
the stream insertion and extraction operators for this string
wrapper did nothing. They were there so the Dstore() and Dload()
functions of the template-form instance had something to call.
Since we didn't stream the Binder we didn't care. Let's redo that
structure so that it can be streamed and we'll redo it with forms
this time just to be different.
// examp502.cpp - link with binder.obj
// rework of examp402.cpp
#define sfile "examp502.txt"
#include <string.h>
#include <iostream.h>
#include "binder.hpp"
struct str {
char *s;
str(const char *cs = (char *)0) // default constructor
{ s = (cs? strdup(cs) : (char *)0); }
str(str& si) // copy initializer
{ s = (si.s? strdup(si.s) : si.s); }
void operator=(str& si) // assignment
{ delete s; s = (si.s? strdup(si.s) : si.s); }
~str() { delete s; } // destructor
};
ostream& operator<<(ostream& os, str& si)
{
int i = (si.s? strlen(si.s) : 0);
os << i << BDRendm;
if (i) os.write(si.s,i);
return os;
}
istream& operator>>(istream& is, str& si)
{
char * D;
int i;
is >> i >> BDRnextm;
if ((D = new char[i+i]) != (char *)0) {
if (i)
is.read(D,i);
D[i] = '\0';
si.s = D;
}
return is;
} #define FType str
#define FBinder StrBdr
#define FBindeR StrBdR
#include "fbinder.hpp"
main()
{
StrBdr sb(BDR_DDELETE | BDR_DNEW | BDR_DSTORE);
sb.ins(new str("line one"));
str s("line two");
sb.insNew(&s);
sb.save(sfile);
StrBdR sB = new StrBdr(sfile);
if (!sB) return 1;
sB->setCurNode(); // reset current node
while (sB->next()) cout << ((str *)*sB)->s << "\n";
delete sB;
return 0;
}
The stream insertion operator writes the out the string's length
followed by the string itself. Notice the use of the Binder's
BDRendm stream manipulator. This manipulator inserts the Binder's
static char member, memberTermChar, between fields on a stream. By
using os.write(), the string may contain the termination character.
The stream extraction operator first reads the length, extracts the
terminator with the BDRnextm manipulator and then reads in the
string with is.read(). The reason the current node must be reset
when the string Binder is reloaded is because the state of the
current node is also persistent!
Also in the last chapter, in examp403.cpp, we created a Binder for
C++ strings in strbdr.cpp. Recall that strbdr.hpp and strbdr.cpp
were cloned from fbinder.hpp and fbinder.cpp, in essence using
forms as a "cookbook." This allowed us to use strings directly
with a strongly type checked Binder without resorting to a wrapper
class. Let's continue with strbdr.cpp and see how Dstore() and
Dload() were overridden. Instead of invoking an overloaded stream
insertion for our data type as is the case with a template or form
"template" instance, Dstore() is defined to do the insertion
directly.void StrBdr::Dstore(ostream& os, voiD D)
{
int i = strlen((char *)D);
os << i << BDRendm;
if (i)
os.write((char *)D,i);
}
Likewise the Dload() function is defined to extract the string
directly from the stream.
voiD StrBdr::Dload(istream& is)
{
char * D;
int i;
is >> i >> BDRnextm;
if ((D = new char[i+i]) != (char *)0) {
if (i)
is.read(D,i);
D[i] = '\0';
}
return (voiD) D;
}
The previous example then becomes:
// examp503.cpp - link with strbdr.obj and binder.obj
// rework of examp502.cpp
#define sfile "examp503.txt"
#include <string.h>
#include "strbdr.hpp"
main()
{
StrBdr sb(BDR_DDELETE | BDR_DNEW | BDR_DSTORE);
sb.ins(strdup("line one"));
sb.insNew("line two");
sb.save(sfile);
StrBdR sB = new StrBdr(sfile);
if (!sB) return 1;
sB->setCurNode(); // reset current node
while (sB->next()) cout << (char *)*sB << "\n";
delete sB;
return 0;
}
Instead of using Binder::save() or the Binder load constructor, you
can insert or extract Binders directly to a stream. The last
example then becomes:
// examp504.cpp - link with strbdr.obj and binder.obj
// rework of examp503.cpp
#define sfile "examp504.txt"
#include <string.h>
#include <fstream.h>
#include "strbdr.hpp"
main()
{
StrBdr sb(BDR_DDELETE | BDR_DNEW | BDR_DSTORE);
sb.ins(strdup("line one"));
sb.insNew("line two");
ofstream os(sfile);
if (!os) return 1;
os << sb;
os.close(); StrBdR sB;
ifstream is(sfile);
if (!is) return 1;
is >> sB;
is.close();
if (!sB) return 1;
sB->setCurNode(); // reset current node
while (sB->next()) cout << (char *)*sB << "\n";
delete sB;
return 0;
}
Can you now see why the (char *) implicit type cast was used on
*sB? That's right, so that the sB Binder itself wasn't streamed
out to cout.
You could of course add stream error logging to both Dstore() and
Dload() by calling the Binder's built-in error function defined as
follows.
void Binder::berror(const char * msg)
{
if (streamDebug)
cerr << endl << msg << endl;
}
StreamDebug is a static public member of Binder defined as follows.
int Binder::streamDebug = 0;
Thus Binder stream error logging can be turned on by setting
streamDebug to a non zero value.Summary
Only strongly type checked Binders can be persistent. A Binder's
complete state, i.e. compare function pointer, current node
setting, flags, etc., persists when stored on a stream. Binder
compare functions must be registered in a consistent order to be
persistent since their id's are assigned in sequence according to
the order of their registration. A Binder having an unregistered
compare function when reloaded from a stream will have a null
compare function pointer, the same as a newly constructed Binder.
Binders can be saved on a stream by using either the save() member
function with a file name or the stream insertion operator with a
file handle. Likewise, a Binder can be reload from a stream by
using either the load constructor or the stream extraction
operator. The stream extraction operation extracts to a Binder
pointer.
Chapter 6
Mutually Owned Containers and Nodes
Thus far we have only looked at simple Binders with simple
persistence. MBinders are strongly type checked Binders that will
only bind objects derived from the Mutual class. Such nodes may be
mutually owned, i.e. belong to more than one container at the same
time. Since MBinders are themselves derived from Mutual, they too
can be multiply contained in other MBinders, etc. A network of
MBinders and any assortment of nodes derived from Mutual can thus
be stored on a stream and later reloaded while the multiple
references to the various elements are automatically resolved!
Function pointers can also be streamed if they are first
registered. Using MBinders and Mutual nodes, you can quickly build
your own highly efficient application framework tool, graph theory
oriented case tool, data base schema representation or whatever
else without worrying about a single link!
To demonstrate, let's use a MBinder to warehouse int's and strings.
Like typeless Binders, MBinders can hold heterogeneous data if it's
derived from Mutual. First we must create a class derived from
Mutual for int's. To do this we copy fmutual.hpp to mint.hpp and
fmutual.cpp to mint.cpp, similar to the way we cookbooked form
Binders for C++ strings. Templates can't be used here because we
want to be able to derive a polymorphic cluster of classes from a
Mutual root, not just simply parameterize Mutual.
We proceed by following the files' commented directions globally
replacing CLASS with Mint, CPTR with MinT, and BASE with Mutual in
both files. We also have to define the ID_Mint macro in mint.hpp
as a unique unsigned integer. The initData() member function has
its only parameter filled in and the first public Mint()
constructor has this parameter filled in also. An I() function has
been added to return Mint's private integer value. After these
modifications we're left with mint.hpp as follows.
#ifndef Mint_HPP
#define Mint_HPP
#ifndef Mutual_HPP
#include "Mutual.hpp"
#endif
#define ID_Mint 2
typedef class Mint * MinT;
#define MinT0 ((MinT)0)
class Mint : public Mutual {
// Mutual must be public for type conversion
// to Mutual for polymorphic handling!
private:
/* Mint-declared data members */
int i;
int initData(int i);
protected:
Mint (initVFTs) : Mutual(initVFTsEtc) {}
virtual void fput(ostream& os);
static MutuaL fget(istream&, MutuaL InstancE);
public:
Mint (int i);
Mint (Mint&);
static void RegisterClass()
{ Mutual::RegisterClass(ID_Mint,Mint::fget); }
virtual int operator=(Mutual&);
virtual MutuaL clone();
virtual unsigned ID() { return ID_Mint; }
virtual ~Mint();
int I() { return i; }
};
#endif /* Mint_HPP */By using the form method we are saved the trouble of having to know
which functions to overload, etc. Never the less, let's look at
the Mint class in detail. The only new data member of Mint is the
integer i. The initData() function must perform the initialization
of i and returns zero if it fails. All this may seem unnecessary
to you for the moment, but an elaborate systematic method has been
worked out in advance for you that allows further extensibility of
the Mint class where each tier in the hierarchy is responsible for
its own data members during stream storing and loading operations
as OOP rightly dictates. (In a class derived from Mint, BASE would
be replaced with Mint instead of Mutual!) The protected Mint()
default constructor simply initializes its virtual function table
and any data that has to be minimally constructed and then calls up
the hierarchy chain to do likewise. The reason this constructor
exists is so that objects derived from Mint can be reconstructed
from a stream. Consider an object derived from Mint being reloaded
from a stream. The object's static fget() protected function gets
called by the stream registry to reload the object. This load
function can't be a constructor since you can't take the address of
a constructor in C++. This drawback is worked around by declaring
fget() as static which does have an address. This fget() calls the
default constructor of the class which in turn calls Mint's default
constructor and so on. Initialization data for Mint isn't
available at this time. Next fget() has the option of reloading
its data members from the stream or calling Mint::fget() first to
load its data members. The order depends on which order the
object's fput() wrote the data out to the stream. What ever the
order decided upon, the object's fget() will get around to calling
its initData() to do the initialization after extracting the
necessary data from the stream just as Mint::fget() will get around
to calling Mint::initData() after extracting its data. The whole
process was designed to separate initialization from construction
thus allowing derived classes the ability to construct the whole
hierarchy while leaving the responsibility of the loading and
subsequent initialization to each tier for its respective data
members. This approach has the added benefit of allowing tier data
to be ordered in various arrangements on the stream. Furthermore,
by forcing an object's normal, non stream construction and its
initialization from stream to both be processed through the same
initData() function we guarantee consistency between the two
approaches. The RegisterClass() function allows us to register the
class for streaming. The order of class registration doesn't
matter since id's are user defined and not automatically supplied
by the registry. The ID() function returns the object's class id.
The rest of the functions are for the benefit of MBinder, namely
operator=() for use with the Binder's Dassign() function, the copy
initializer constructor called in turn by clone() for use with the
Binder's Dnew() function, and of course the destructor for use with
the Binder's Ddelete() function or by itself.
Next we turn to the class derived from Mutual for strings (mstr.hpp
and mstr.cpp). The mstr.cpp file requires a little more editing
than the mint.cpp file since strings don't load themselves from
streams the way we want them to. Let's examine mstr.cpp. One
function at a time will be listed interspersed with comments.
#ifndef Mstr_HPP
#include "mstr.hpp"
#endif
#include <string.h>
int Mstr::initData(char *s, int dup)
{
this->s = ((dup && s)? strdup(s) : s);
if (this->s) return 1; // success
return 0;
}
Both the string and int parameters have been added to initData().
The initData() function duplicates the string if requested by
"dup."
void Mstr::fput(ostream& os)
{
Mutual::fput(os);
int i = strlen(s);
os << i << Mendm;
if (i && os) os.write(s,i);
if (!os)
error("unable to store Mstr "
"data on stream");
}Like StrBdr::Dstore() in strbdr.cpp and examp503.cpp, fput() first
outputs the string length followed by the string. Recall that the
Binder had a stream error function. Well Mutual has a stream error
function also and this time we're using it.
The fget() function is the hardest, but remember most of the work
has been done for you when you clone fmutual.cpp. Recall from our
discussion of Mint::fget() that an fget() function has to operate
in two modes. If the InstancE pointer is NULL that means that the
fget() function is being called by the stream registry as the
constructor of the object being loaded from the stream. If it is
not NULL than it is being called from a derived class' fget() to
initialize the Mstr's tier data and not as a constructor! (We must
code fget() in fashion that allows Mstr to be extended!)
MutuaL Mstr::fget(istream& is, MutuaL InstancE)
{
int newed;
MstR thiS;
char * s;
int i;
is >> i >> Mnextm;
if (!is) {
serror("unable to load Mstr "
"data from stream",
ID_Mstr);
return MutuaL0;
}
if ((s = new char[i+i]) != (char *)0) {
if (i) {
is.read(s,i);
if (!is) {
delete s;
return MutuaL0;
}
}
s[i] = '\0';
}
if (InstancE) {
newed = 0;
thiS = (MstR) InstancE;
}else {
if ((thiS = new Mstr(initVFTsEtc))
== MstR0) {
serror("unable to construct "
"new Mstr for "
"loading",
ID_Mstr);
return MutuaL0;
}
newed = 1;
InstancE = (MutuaL) thiS;
}
if (!Mutual::fget(is,InstancE)) {
if (newed)
delete (voiD) thiS;
return MutuaL0;
}
if (!thiS->initData(s,0)) {
serror("unable to initialize Mstr "
"from reloaded stream data",
ID_Mstr);
if (newed)
delete (voiD) thiS;
return MutuaL0;
}
return InstancE;
}
The string's length is read first from the stream followed by the
string itself. Then InstancE is checked to see if this fget() is
being called as a constructor or as a tier initializer. After this
has been decided and taken care of, the base class fget() is called
to take care of its tier data. In our case, Mutual::fget() doesn't
do anything but it does provide the coding style for calling any
base class fget(). Notice that our arrangement has the base class
data being stored on the stream after the derived class data. If
you need it the other way around move all the string's extraction
code up to but not including the "if (InstancE)" line to
immediately after the "if (!Mutual::fget(is,InstancE))" block.
Your fput() function would also have to call base::fput(os) before
inserting its tier data in the stream. Notice that because a
buffer was allocated for the string here, initData() is called with
the duplication option turned off. This is why initData() had the
option!Mstr::Mstr (char *s)
: Mutual(initVFTsEtc)
{ (void) initData(s); }
A string parameter is added to the constructor just like an int
parameter was added in mint.hpp. InitData()'s dup parameter
defaults to "on."
Mstr::Mstr(Mstr& c) : Mutual(c)
{
// copy initializer constructor
if (c.S())
s = strdup(c.S());
else
s = (char *) 0;
}
The copy initializer constructor is defined to duplicate the string
of the original Mstr if there is one. Mstr::S() returns its string
pointer which always points to a dynamically allocated string!
int Mstr::operator=(Mutual& m)
{
delete s;
s = (char *) 0;
if ((s = ((MstR)&m)->S()) != (char *)0)
s = strdup(s);
return 1; // enabled
}
Remember, assignment differs from initialization in that the old
string must be properly disposed of, i.e. deleted. The assignment
operator is an overloaded virtual function originating in the
Mutual class, hence the Mutual reference parameter.
MutuaL Mstr::clone()
{
// invokes copy initializer constructor
return (MutuaL) new Mstr(*this);
}
Nothing is modified in clone().Mstr::~Mstr()
{
delete s;
}
The destructor must of course delete the string.
This seems like a lot of work to make something mutually ownable,
persistent, and extensible. But you must remember that C++ has yet
to address persistence in its language definition. If you use
fmutual.hpp and fmutual.cpp to cookbook your own it will go
smoothly. At last we're ready to look at our example.
// examp601.cpp - link with binder.obj, mutual.obj,
// mint.obj, and mstr.obj
#define bfile "examp601.txt"
#include <string.h>
#include "mint.hpp"
#include "mstr.hpp"
// display integers and strings
void display(MutuaL M)
{
switch (M->ID()) {
case ID_Mint:
cout << ((MinT)M)->I() << endl;
break;
case ID_Mstr:
cout << ((MstR)M)->S() << endl;
break;
default:
cout << "unknown" << endl;
break;
}
}// sort integers and strings
int mintstrcmp(MutuaL M1, MutuaL M2)
{
// integers sorted to the front
// strings sorted to the rear
if (M1->ID() == ID_Mint)
if (M2->ID() == ID_Mint)
return ((MinT)M1)->I()
- ((MinT)M2)->I();
else
return -1;
else
if (M2->ID() == ID_Mint)
return 1;
else
return strcmp(
((MstR)M1)->S(),
((MstR)M2)->S());
}
main()
{
Mint::RegisterClass();
Mstr::RegisterClass();
MBinder b1;
b1.push(new Mstr("Hello LDB!"));
b1.insQ(new Mstr("Goodbye linked"));
b1.insQ(new Mstr("list programming!"));
b1.insQ(b1.rear());
b1.insQ(new Mstr(
"Line above tests multilinking!"));
for (int i = 3; i; i--)
b1.insQ(new Mint(i));
cout << "\n\nMBinder of streamable integers"
<< " and strings!\n\n";
b1.forEach((BDRapplY)display);
cout << "\n\nPress enter to continue ...";
cin.get();
b1.setComP((BDRcomP)mintstrcmp);
Binder::RegisterComP((BDRcomP)mintstrcmp);
(void) b1.save(bfile);
b1.allDel();
MBinder b2(bfile);
cout << "\nStreamed and "
<< "reloaded MBinder with "
<< "multiple links maintained "
<< "\nand sorted with streamed "
<< "compare fnc, ints in front:"
<< " \n";
Restream();
// Don't load again from
// any stream without
// restreaming StreamRegistry!!!
b2.sort();
b2.forEach((BDRapplY)display);
return 0;
}
Notice how the display function takes the Mutual pointer and reads
the ID() to determine if we have a mutually owned int or string,
i.e. Mint or Mstr. The compare function works the same way making
int's less than strings, thus forcing int's ahead of strings when
sorting. Once we get into main(), both the Mint and Mstr classes
are registered with the stream registry. Then several strings are
created and queued. The string "list programming!" is requeued for
a second time to show the same Mstr instance being bound twice in
the same Binder. Don't worry, it won't be stored or reloaded from
stream twice. Nor will it be deleted twice. Next, some integers
are queued and the forEach iterator applies the display function to
each node of the Binder. Then the Binder is saved on stream before
deleting it. The compare function is of course registered before
streaming. The Binder is then reloaded from stream, sorted and
displayed.
The Restream() function clears the stream registry's holding pen of
any remaining left over multilink instances. If any are remaining
and the static StreamRegistry::debug is turned on (non zero), these
left over errors are reported on cerr.
The Mutual class also has two static switches, refDebug and
streamDebug. When refDebug is turned on any over/under linking
attempts to a Mutual object is reported on cerr. When streamDebug
is turn on the Mutual stream error and warn functions report on
cerr also. Mutual objects also inherit a restream() function which
must be called between streaming sessions. In our example, we
didn't need to call b1.restream() since we disposed of b1 without
attempting to save it again on a stream. But if we had called
b1.restream() with streamDebug turned on, any node that had more
reference links than storage attempts would have complained via
Mutual::error() reporting "restream: streamed less than
referenced". For more details see the reference chapter.
A Mutual object can't be stored via the stream registry if it
hasn't been linked to. Each time a node is bound in a MBinder its
link count is incremented. Each time a node is released from a
MBinder its link count is decremented. Remember, Mutual::refDebug
controls the reporting of over/under linking. How was it then that
b1, a MBinder derived from Mutual, in our example could be streamed
when it wasn't linked to anything nor registered? The answer is
that it wasn't stored via the stream registry, but rather via its
inherited Binder save() function. Actually a Binder is stored on
the stream instead of an MBinder. That's okay, because its nodes
are processed by the stream registry anyway. Any MBinders queued into b1 would have been stored via the stream registry as MBinders
as expected. The MBinder class would have to be registered of
course. If we used the stream insertion and extraction operators
instead of the save() and load constructor, the stream registry
would be called into play instead of the inherited Binder streaming
functions. You can not mix the two methods since the Binder
functions stream a Binder and the stream registry streams an
MBinder, i.e. the Mutual id and multilink data. The stream
registry approach is used in examp602.cpp, an excerpt of which
follows.
MBinder::RegisterClass();
...
ofstream os(bfile);
if (!os) {
b1.allDel();
return 1;
}
b1.link();
os << b1;
os.close();
b1.allDel();
ifstream is(bfile);
if (!is)
return 1;
MutuaL M;
is >> M;
is.close();
if (!M)
return 1;
if (M->ID() != ID_MBinder)
return 1;
MBindeR B2 = (MBindeR) M;
...
delete B2;The MBinder class is now registered. Notice that b1 had to be
linked to before storing otherwise the stream registry would have
reported "more stores attempted than links." Only Mutual objects
may be extracted from a Mutual object stream. It is not known
ahead of time whether or not the Mutual object happens to be an
MBinder. The id is checked to verify that we have loaded an
MBinder. Only then is the Mutual pointer assigned to the MBinder
pointer B2, reflecting the fact that reloaded objects are
dynamically allocated and reminding us that B2 must be deleted
before exiting main().
Well then, how does multilink resolution actually work? When an
instance of a class derived from Mutual, let's call it X, is
referenced by more than one object, all of which are also being
stored onto a stream, the first object that attempts to store X
will succeed while the rest will automatically store only a link
reference to X. When reloading X, X will be automatically held in
a holding pen inside of the stream registry if it is multiply
referenced. When the additional objects referencing X are
reloaded, the links to X are recovered. When the last reference to
X is loaded and that last link to X is reconstructed, X is released
automatically from the holding pen. This mechanism prevents
multiple copies of X from being stored on the stream and thus
multiple copies being reloaded. The holding pen (a Binder "data
base" of multiple references) is self cleaning and thus requires no
attention from the programmer. Restream() double checks to see if
the holding pen is clean, complaining if it has to clean up left
overs. Mutual::restream() complains if X has more links then
attempts at storing since the last restream() operation. The
MBinder::restream() function automatically calls each of its node's
restream() function.
Summary
Derive all your mutually owned, persistent objects from Mutual by
cloning fmutual.hpp and fmutual.cpp (following the commented
directions). Use MBinder to bind Mutual objects. Since MBinder is
derived from Mutual itself, it can be readily bound into one or
more MBinders. It's also permissible to use Mutual objects without
MBinders (see reference chapter). You must register each class
that will be streamed, however. The order of class registration is
not important since id's are user defined. Base classes, that have
no instances of themselves streamed, do not have to be registered.
The act of registration is simply the recording of a class' id with
its associated loader function, i.e. fget(). The intermediate, non
instantiated base class fget()'s are not called from the stream
registry but rather from the fget() of the object being loaded.
See the StreamRegistry section in reference chapter for details on
streaming function pointers. All streamed data pointers must point
to registered Mutual objects. Be sure to call Restream() to reset
the stream registry between each streaming session. You must also
call Mutual::restream() between sessions for each object streamed.
MBinder::restream() automatically calls each node's restream()
function. You can set Mutual::streamDebug to a non zero value to
have streaming errors reported on cerr. Likewise you can set
Mutual::refDebug to report over/under linking attempts to a Mutual
object. The StreamRegistry::debug switch enables the reporting of
holding pen errors, etc.
Chapter 7
Reference
-----------------------------------------------------------------
Class: Binder binder.hpp
-----------------------------------------------------------------
Inherited by: MBinder
A Binder is an elastic array of void pointers having the added
behaviors of stack, queue, deque, list, etc. To achieve strong
type checking use templates or forms. Only strongly type checked
Binders can be persistent.
Template usage:
#include "tbinder.hpp"
TBINDER(TYPE, TYPEBINDER, TYPEBINDERPTR);
where
TYPE is the data type to check for,
TYPEBINDER is the name of this new Binder,
and TYPEBINDERPTR is a pointer to the new Binder.
For example, to create a strongly type checked Binder for
integers:
#include "tbinder.hpp"
TBINDER(int,IntBdr,IntBdR);
To use the template TYPE must have:
a default constructor,
a copy initializer constructor,
i.e. TYPE(TYPE&)
a destructor,
an assignment operator, i.e. operator=(),
a stream insertion operator,
i.e. ostream& operator<<(ostream&,TYPE&),
and a stream extraction operator,
i.e. istream& operator>>(istream&,TYPE&).
Class: Binder binder.hpp
-----------------------------------------------------------------
If your compiler doesn't support C++ templates or your data
type doesn't meet these requirements use forms instead.
Form usage:
#define FType int
#define FBinder IntBdr
#define FBindeR IntBdR
#include "fbinder.hpp"
Fbinder.hpp leaves FType, FBinder, and FBindeR undefined thus
allowing multiple form "templates" to be used in the same
file.
FType must meet the same requirements as the template's TYPE
parameter. If not, clone the fbinder.hpp and fbinder.cpp
files and edit these according to the commented directions.
Be sure to read chapters 4 and 5.
Members, private:
unsigned lowLimit, lowThreshold, first;
voiDV linkS;
unsigned limit, delta, nodes;
unsigned maxNodes, curNode, flags;
BDRcomP comP;
The value of "limit" is the current size of the array pointed
to by "linkS". The value of "delta" is how much the array
will grow upon overflow. "Nodes" is the number of cells in
the array that are currently occupied. "MaxNodes" is the
upper bound for the growth of "limit". "LowTheshold" is the
number of logical cells the Binder has to shrink to before
shrinking the physical size of the array pointed to by
"linkS". When compacted the array will be shrunk to the new
limit of "lowLimit". "First" maps the Binder's logical cell
zero into a starting cell position in the physical array
pointed to by "linkS". "CurNode" is the current node
associated with the Binder's list behavior.
Class: Binder binder.hpp
-----------------------------------------------------------------
"Flags" has the following bit definitions.
#define BDR_SORTED 0x01U
#define BDR_BIND_ONLY 0x00U
#define BDR_DASSIGN 0x02U
#define BDR_DNEW 0x04U
#define BDR_DDELETE 0x08U
#define BDR_DSTORE 0x10U
The BDR_SORTED flag indicates whether the Binder is still
sorted since the last sort operation. BDR_DASSIGN enables
primitives ending with the "Asg" suffix. BDR_DNEW enables the
primitives ending with the "New" suffix. BDR_DDELETE enables
the primitives with "Del" or "del" in their names. It also
causes the destructor to call allDel() instead of allRmv().
BDR_DSTORE enables the save() and overloaded stream insertion
operators. The comP variable holds the current compare
function pointer.
static Binder& comPv;
static unsigned comPID(BDRcomP comP);
static BDRcomP comPLU(unsigned ID);
The comPv is a vector of registered compare function pointers.
The id of a compare function is its index + 1. ComPID()
returns the id of a compare function pointer and comPLU()
looks up an id returning its compare function pointer. These
members are used inside store() and load() to stream compare
functions.
int initData(unsigned flags,
unsigned maxNodes,
unsigned limit,
unsigned delta);
The initData() initializer is called by the Binder
constructors and the stream load() function guaranteeing
consistency between the two approaches to Binder creation.
Class: Binder binder.hpp
-----------------------------------------------------------------
Functions, protected:
Binder (initVFTs) {}
This default constructor provides a mechanism for stream
loader functions to construct an object's hierarchy
without initializing it. The parameter is a dummy to
present a style for latter extensibility that will insure
that an unambiguous default constructor is available.
void destruct();
You must override the Binder destructor in any descendant
that overrides Ddelete() or Ddetach(), i.e.
virtual ~DerivedFromBinder()
{ Binder::destruct(); }
This is because the base destructors are called after
virtual function tables are reset to their default values
for the base level. This means that Binder::~Binder()
calls Binder::Ddetach() and perhaps Binder::Ddelete()
instead of the intended DerivedFromBinder::Ddetach() or
DerivedFromBinder::Ddelete(). By performing the actual
destruction in the destruct() function the overridden
destructor can be simply coded inline as shown.
virtual voiD Dassign(voiD, const voiD)
{ return voiD0; }
Called by atPutAsg(), atGetAsg(), popAsg(), etc. to copy
data between a node and an external object. Template and
form instances override Dassign() redefining it to call
the operator=() function for your data type. It is
guaranteed that Dassign() is never called unless the
BDR_DASSIGN flag is set and the parameters are not NULL!
If Dassign() returns NULL the calling primitive
gracefully fails leaving the Binder in an unaltered
state. MBinders use this function to call Mutual's
operator=() function.
Class: Binder binder.hpp
-----------------------------------------------------------------
virtual voiD Dnew(const voiD) { return voiD0; }
Called by atInsNew(), pushNew(), atPutNew(), etc. to
clone the data given as the parameter. Template and form
instances override Dnew() redefining it to call the copy
initializer constructor for your data type. It is
guaranteed that Dnew() is never called unless the
BDR_DNEW flag is set and the parameter is not NULL. If
Dnew() returns NULL the calling primitive gracefully
fails leaving the Binder in an unaltered state. MBinders
use this function to call Mutual's clone() function.
virtual void Ddelete(voiD D) { delete (voiD) D; }
Called by atDel(), allDel(), popDel(), del(), etc. to
delete Binder nodes as they are removed from the Binder.
Template and form instances override Ddelete() redefining
it to call the appropriate destructor for your data type.
It is guaranteed that Ddelete() is never called unless
the BDR_DDELETE flag is set and the D parameter is not
NULL. MBinders carry out the deletion of Mutual nodes
only when their refCount's are zero.
virtual int Dattach(voiD) { return 1; }
Called by any Binder primitive that attempts to bind
data, e.g. atIns(), push(), etc.. This allows the data
to become aware of the binding process by overriding
Dattach(). The voiD parameter is a pointer to the data
about to be bound and is guaranteed never to be NULL! If
and only if the data can't attach itself to the Binder
should zero be returned. Any overriding function should
only link itself to the Binder and should not use the
implicit "this" pointer to access the Binder within the
overriding function. This restriction applies because
Binder data may be in a transition state when Dattach()
is called. It is however permissible to store the "this"
pointer within the data being bound for later use in
accessing Binder members. Template and form instances do
not override this function. MBinders use this virtual
function hook to link to their Mutual nodes.
Class: Binder binder.hpp
-----------------------------------------------------------------
virtual void Ddetach(voiD) { return; }
Ddetach() is called by any Binder primitive that attempts
to unbind data, e.g. atDel(), pop(), etc.. The voiD
parameter is never NULL! Once called, the node must
consider itself detached from the Binder! Template and
form instances do not override this function. MBinders
use this virtual function hook to unlink from their
Mutual nodes.
static void sberror(const char * msg);
If Binder::streamDebug is non zero the message parameter
is streamed to cerr. This Binder error function is
static so that it can be called from the static load()
function.
virtual void berror(const char * msg);
If Binder::streamDebug is non zero the message parameter
is streamed to cerr. This function is called from the
store() function.
virtual void Dstore(ostream&, voiD) {}
Dstore() is called for each node after the Binder's
header is stored on the stream. The voiD parameter is
guaranteed never to be NULL. Template and form instances
override this function redefining it to invoke the
overloaded stream insertion operator for your data type.
MBinders use this function to invoke operator<<
(ostream&, Mutual&) on Mutual nodes.
Class: Binder binder.hpp
-----------------------------------------------------------------
virtual voiD Dload(istream&) { return voiD0; }
Dload() is called for each node on a stream after the
Binder's header is loaded. The Binder binds what Dload
returns. Template and form instances override this
function redefining it to invoke the default constructor
for a temporary variable of your data type, which it then
invokes the overloaded stream extraction operator on.
The new operator is then applied to the copy initializer
constructor for your data type using this temporary
variable as its parameter. The resultant pointer is
returned. MBinders use this function to invoke the
operator>> (istream&, MutuaL&) function which calls the
stream registry to extract the class id from the stream,
lookup the load function, and load the instance. Note
that by extracting to a pointer (implicit allocation)
that construction and initialization of a temporary
variable is bypassed!
virtual void store(ostream& os);
Called by the Binder:: save() function, the stream
insertion operators defined by the template and form
instances, and the MBinder:: fput() function to store the
Binder on the os stream. Store() is never called unless
the BDR_DSTORE flag is set. The Binder's header is saved
first followed by its nodes. The nodes are saved via
Dstore(). Template and form instances, and MBinders do
not override this store() function. Instead they
override Dstore(). The purpose of overriding the store()
function would be to store additional header information
contained in a class derived from Binder. The MBinder
supplied stream insertion operator invokes the stream
registry's fput() which stores id and multilink
information ahead of the Binder information, then calls
MBinder:: fput() which in turn calls this store function.
Since the stream registry's fput() takes care of the
additional data inherited from Mutual, MBinder has no
need to override this store() function. Note that the
MBinder:: save() function bypasses the stream registry
effectively storing a Binder instead of an MBinder.
Class: Binder binder.hpp
-----------------------------------------------------------------
static BindeR load(istream& is, BindeR thiS);
The load() can be called by either the Binder's load
constructor via vload() or a derived class' static load()
function. Load() extracts the necessary Binder
initialization parameters from the stream and calls
initData(). If "thiS" is NULL, load() allocates a new
Binder using the protected default constructor,
Binder(initVFTs), before calling initData(). The
Binder's nodes are then extracted from the stream by
repeatedly calling Dload(). The state of the current
node, flags, and any compare function is also restored.
The BDR_DDELETE flag is raised if not set already since
reloaded nodes are dynamic! If load() fails to load the
Binder from stream, it returns BindeR0, the NULL pointer.
Failure to load a Binder node does not constitute a
failure to load the Binder. Load() uses the sberror()
function to report errors.
int vload(const char * filename, BDRsloaD sloaD, voiD thiS);
The load constructor, i.e. Binder(const char * filename),
calls vload() with the static load() function. Vload()
prepares the stream and calls load(). If anything fails
vload() leaves the Binder in an acceptable, default
constructed state. Any class derived from Binder can
define a load constructor by simply calling vload() with
its static load() function. This in effect "virtualizes"
the load constructor.
Class: Binder binder.hpp
-----------------------------------------------------------------
Miscellaneous, public:
static int streamDebug;
When non zero, streamDebug enables stream error reporting
on cerr. Used by sberror() and berror() functions. Its
default value is zero.
static char memberTermChar;
Used as the data member delimiter by the stream
manipulators, BDRendm and BDRnextm. The default value is
'\n', the new line character.
Though not members of the Binder proper, the following stream
manipulators are used to insert/extract the memberTermChar.
ostream& BDRendm(ostream& os);
istream& BDRnextm(istream& is);
static void RegisterComP(BDRcomP comP);
Use this function to register Binder compare functions
for streaming. Since function id's are assigned
sequentially, always register your compare functions in
the same order to insure that id's read from stream are
meaningful. RegisterComP() uses the comPv Binder to
record functions.
static void ForgetComPs() { comPv.allDel(); }
After all Binder streaming is accomplished within your
program it is permissible to forget their registrations
to free up a little heap space. ForgetComPs() deletes
all entries recorded with RegisterComP().
Class: Binder binder.hpp
-----------------------------------------------------------------
Constructor and Destructor primitives, public:
Binder(unsigned flags = BDR_BIND_ONLY,
unsigned maxNodes = BDR_MAXNODES,
unsigned limit = BDR_LIMIT,
unsigned delta = BDR_DELTA)
{ (void) initData(flags,maxNodes,limit,delta); }
This constructor defaults to initializing a Binder so
that node assigning, cloning, and deleting primitives are
inhibited, e.g. atGetAsg(), atInsNew(), atDel(), etc.,
see Binder:: flags. Thus the destructor will call
allRmv() instead of allDel(). MBinders set the flags
parameter to the default value of (BDR_DDELETE |
BDR_DSTORE), thus enabling deleting and storing
primitives and forcing the destructor to call allDel()
instead of allRmv(). Don't leave dynamically allocated
nodes in Binder upon destruction unless the BDR_DDELETE
is set or they will be left dangling! Likewise don't
bind statically allocated nodes in a Binder that has its
BDR_DDELETE flag set or the destructor will try to
deallocate them! The Binder is limited to "maxNodes"
starting off with an internal physical array big enough
to hold "limit" nodes which will grow or shrink at
"delta" rate. BDR_LIMIT is defined as 20 and BDR_DELTA
as 10 while BDR_MAXNODES is defined as ((unsigned)
(UINT_MAX / sizeof(voiD))). "VoiD" is typedef'ed as a
pointer to void.
Binder (voiDV argv, unsigned argc = 0,
unsigned flags = BDR_BIND_ONLY);
This constructor takes "argv", a vector of void pointers,
and builds a Binder by copying each void pointer into a
cell of the Binder's array. If "argc" is zero then
"argv" is expected to be NULL terminated. This
constructor calls the first constructor with "flags" to
do the actual Binder initialization then proceeds to loop
through the vector queuing the void pointers.
Class: Binder binder.hpp
-----------------------------------------------------------------
Binder (const char *filename)
{ (void) vload(filename,(BDRsloaD)Binder::
load,this); }
This constructor will load a Binder from a file. If the
operation fails the Binder will be left in an acceptable
default constructed state, i.e. no nodes. The state of
the current node, flags, and any compare function is also
restored. The BDR_DDELETE flag is raised if not set
already since its reloaded nodes are dynamic!
int save(const char *filename);
Use this function to save a Binder in a file. Only
strongly type checked Binders can be streamed, i.e
template and form instances, MBinders, or you own derived
Binders that override the protected Dstore() and Dload()
virtual functions. Be sure to register the compare
function if you want it to persist, see RegisterComP().
The state of the current node setting and flags will also
persist upon reloading. The BDR_DSTORE flag must be set
in order for save to proceed. If successful, save() will
return a non zero value.
voiDV vector();
Returns a dynamically allocated, NULL terminated, vector
of void pointers to the nodes currently in the Binder.
The order of nodes in the Binder dictates the order of
pointers in the vector. "VoiDV" is typedef'ed as a
pointer to a void pointer, i.e. "voiD *." Remember to
delete the returned vector when you are done with it!
The nodes of the Binder are not alerted to the fact that
they are pointed to by this vector, i.e. Dattach() is not
called - Dattach() is only called to "attach" data to a
Binder. Thus the Mutual nodes in an MBinder are not
linked to the returned vector!
Class: Binder binder.hpp
-----------------------------------------------------------------
virtual ~Binder() { destruct(); }
See the entry for destruct() near the beginning of the
protected section.
Housekeeping primitives, public:
unsigned Limit() { return limit; }
Returns the number of cells (in use plus available) in
the Binder's internal physical array.
unsigned setLimit(unsigned newLimit);
Adjusts the size of the Binders internal physical array
if possible returning the new limit or zero otherwise.
It's not possible to set the new limit below the number
of nodes currently in the Binder or below delta or above
maxNodes.
unsigned pack() { return setLimit(nodes); }
Shrink the Binder's internal physical array to be just
big enough to hold the nodes. Returns the number of
nodes in the Binder which is also the size of the
Binder's internal physical array if successful, otherwise
zero is returned. The logical array is always contiguous
within the physical array, i.e. no holes. The Binder may
map the logical array's index 0 to any physical index,
wrapping the logical array as necessary. However, after
a successful pack(), the logical and physical arrays are
identical, i.e. no wrap.
unsigned Delta() { return delta; }
Returns the size that the Binder will grow or shrink upon
overflow or underflow respectively.
Class: Binder binder.hpp
-----------------------------------------------------------------
unsigned setDelta(unsigned newDelta = BDR_DELTA);
Set the granularity of growth/shrinkage for the Binder's
physical array. BDR_DELTA is defined as 10. Returns the
value of the new delta if successful otherwise zero is
returned.
unsigned Nodes() { return nodes; }
Returns the number of nodes currently bound within the
Binder, i.e. size of the logical array.
unsigned MaxNodes() { return maxNodes; }
Returns the maximum number of nodes the Binder is ever
allowed to hold.
unsigned setMaxNodes(unsigned newMaxNodes = BDR_MAXNODES);
Set the maximum number of nodes a Binder is allowed to
hold returning this new value. NewMaxNodes must not be
below "limit" (current size of the Binder's internal
physical array) or above BDR_MAXNODES in order to be
valid. Zero is returned if newMaxNodes is invalid.
BDR_MAXNODES is defined as ((unsigned) (UINT_MAX /
sizeof(voiD))). "VoiD" is typedef'ed as a pointer to
void.
unsigned vacancy() { return maxNodes - nodes; }
Returns the number of nodes that can yet be added to the
Binder.
unsigned vacancyNonElastic() { return limit - nodes; }
Returns the number of nodes that can still be added to
the Binder without undergoing an expansion of the
Binder's internal physical array.
Class: Binder binder.hpp
-----------------------------------------------------------------
unsigned Flags(unsigned flags = BDR_ALL_FLAGS)
{ return (this->flags & flags); }
Returns the specified flag(s). BDR_ALL_FLAGS masks all
flags. For flag definitions see "flags" in the private
section of Binder.
unsigned setFlags(unsigned flags)
{ return (this->flags |= flags); }
Allows you to raise any Binder flag(s). Be sure you
don't set the BDR_DDELETE flag if the Binder has
statically allocated nodes. It is sometimes useful to
sort the Binder using one compare function then scan it
with a slightly different "filter" compare function.
Upon setting this new compare function the BDR_SORTED
flag is automatically reset but you can set it again with
setFlags()!
unsigned resetFlags(unsigned flags)
{ return (this->flags &= ~flags); }
Allows you to reset any Binder flag(s). Be sure you
don't reset the BDR_DDELETE flag if you are counting on
the Binder's deleting any remaining nodes. The Binder's
destructor calls allRmv() unless the BDR_DDELETE flag is
set in which case it calls allDel().
Class: Binder binder.hpp
-----------------------------------------------------------------
Binder& operator<<(Binder& (*manipulator)(Binder&))
{ return (manipulator?
(*manipulator)(*this)
: *this); }
This overloaded operator allows user defined manipulators
to be applied to a Binder in the same fashion iomanip.h
allows manipulators for streams. For example:
// examp701.cpp - link with binder.obj
#include "binder.hpp"
Binder& clear(Binder& b)
{
if (b.Flags(BDR_DDELETE))
b.allDel();
else
b.allRmv();
return b;
}
char *v[] = {"line one ", "line two ", 0 };
main()
{
Binder b ((voiDV) v);
b << clear;
b.atIns(0,"only line");
while (++b)
cout << (char *) b.get() << "\n";
return 0;
}
Class: Binder binder.hpp
-----------------------------------------------------------------
Elastic Array primitives, public:
voiD atIns(unsigned n, voiD D);
Inserts a new cell at index n pushing back all cells
starting at n possibly expanding the Binder's internal
physical array if necessary. The specified void pointer
is copied to the new nth cell and is also returned. If
the operation fails the Binder is left unchanged and
voiD0 (NULL pointer to void) is returned. If D is NULL
than a cell is not added.
voiD atInsNew(unsigned n, const voiD D);
Same as atIns() except the data pointed to by D is cloned
via Dnew() and the pointer to the clone is inserted
instead of the original. The inserted pointer is
returned if all goes well otherwise voiD0, the NULL void
pointer, is returned. The BDR_DNEW flag must be set to
enable this primitive. The default Dnew() for a typeless
Binder always returns NULL causing this primitive to fail
even if the BDR_DNEW flag is set.
voiD atRmv(unsigned n);
Return the node pointer stored in the nth cell destroying
this position pulling all cells starting at n + 1 forward
perhaps collapsing the Binder's internal physical array
if the remaining number of cells falls below an
internally calculated threshold (lowThreshold). If the
operation fails, e.g. n is out of range, voiD0 (NULL
pointer to void) is returned.
void allRmv();
Destroys all the cell positions discarding the node
pointers without deleting the nodes.
Class: Binder binder.hpp
-----------------------------------------------------------------
int atDel(unsigned n);
The BDR_DDELETE flag must be set to enable this
primitive. Performs the same function as atRmv() except
the discarded node is deleted via the Ddelete() protected
virtual function. A non zero value is returned to
indicate success.
voiD atDelAsg(unsigned n, voiD D);
The BDR_DDELETE and BDR_DASSIGN flags must both be set to
enable this primitive. Performs the same function as
atDel() except the contents of the node is copied to the
D parameter location before the node is deleted. The
copy operation is performed by the Dassign() protected
virtual function. If the D parameter is NULL, or the
Dassign() function fails, or for any other reason, e.g.
n is out of range, etc., the primitive fails and NULL is
returned. Otherwise the value of the D parameter is
returned. The default Dassign() for a typeless Binder
always returns a failure indication.
int allDel();
Repeatedly calls atDel(0) if the BDR_DDELETE flag is set.
If the BDR_DDELETE flag is not set, zero is returned to
indicate failure.
voiD atPut(unsigned n, voiD D);
Overwrites the nth cell's pointer with D, the new node
pointer, returning D. If D is NULL or n is out of range
then nothing is overwritten and voiD0 (NULL void pointer)
is returned. Before the old pointer is overwritten, it
is deleted via Ddelete if the BDR_DDELETE flag is set.
The subscript range of a Binder is always 0 to nodes - 1,
hence the concept of an elastic array.
Class: Binder binder.hpp
-----------------------------------------------------------------
voiD atPutNew(unsigned n, const voiD D);
The BDR_DNEW flag must be set to enable this primitive.
Performs the same function as atPut() except the data
pointed to by D is cloned via Dnew() and the pointer to
the clone is atPut()'ed. If successful the clone's
pointer is returned, otherwise NULL is returned.
voiD atPutAsg(unsigned n, const voiD D);
The BDR_DASSIGN flag must be set to enable this
primitive. Copies the data pointed to by the D parameter
to the node bound at index n. The actual copying is
performed by the Dassign() protected virtual function.
If successful, the pointer to the bound node is returned.
The default Dassign() for a typeless Binder always
returns a failure indication.
voiD atGet(unsigned n);
Returns the node pointer stored in the nth cell.
voiD operator[](unsigned n) { return atGet(n); }
As shown by its inline definition, the overloaded
subscripting operator provides a convenient notation for
calling atGet().
voiD atGetAsg(unsigned n, voiD D);
Provides the same functionality as atGet(), except the
contents of the node bound at index n is copied via
Dassign() to the location pointed to by the D parameter.
If successful the value of the D parameter is returned.
The BDR_DASSIGN flag must be set to enable this
primitive. Also, the default Dassign() for a typeless
Binder always returns a failure indication.
Class: Binder binder.hpp
-----------------------------------------------------------------
voiD atXchg(unsigned n, voiD D);
Returns the node pointer stored in the nth cell if D is
not NULL and n is in range otherwise voiD0 (NULL) is
returned. D is written to the nth cell.
unsigned index(const voiD D);
Find the index of the cell containing the pointer with
the same value as D. If D is not bound in the Binder
then index() returns BDR_NOTFOUND which is defined as
BDR_MAXNODES which is in turn defined as ((unsigned)
(UINT_MAX / sizeof(voiD))). Since the cells of a binder
are indexed from 0 to nodes - 1, BDR_NOTFOUND will never
be an index of a valid Binder cell.
int forEach(BDRapplY B, voiD M = voiD0, voiD A = voiD0);
This Binder iterator applies the function specified by
the function pointed to by B to each of its nodes. M and
A are user definable since they are simply passed on to
the B specified function along with the pointer to the
node being processed. ForEach() returns true if B is not
NULL and there are nodes to process. The function
pointer type is defined as:
typedef void (*BDRapplY)
(voiD D, voiD M, voiD A);
Class: Binder binder.hpp
-----------------------------------------------------------------
Stack, Queue, and Deque primitives, public:
voiD push(voiD D) { return atIns(0,D); }
Pushes the node pointer onto the stack returning that
pointer if successful otherwise it returns voiD0, the
NULL void pointer.
voiD pushNew(const voiD D) { return atInsNew(0,D); }
Same as push() except the data pointed to by D is cloned
and a pointer to the clone is instead pushed.
voiD pop() { return atRmv(0); }
Pop the stack returning the node pointer or voiD0, the
NULL void pointer, if no nodes are present.
Binder& operator>>(voiD& D) { D = atRmv(0); return *this; }
Provides a convenient way to call pop().
int popDel() { return atDel(0); }
Same as pop() except the popped pointer is deleted via
the Ddelete() function. The BDR_DDELETE flag must be set
to enable this primitive.
voiD popDelAsg(voiD D) { return atDelAsg(0,D); }
Same as popDel() except the contents of the node being
delete is copied via Dassign() to the location pointed to
by D. If successful D is returned otherwise NULL is.
The BDR_DASSIGN and BDR_DDELETE flags both must be set to
enable this primitive.
Class: Binder binder.hpp
-----------------------------------------------------------------
voiD top() { return atGet(0); }
Returns the pointer to the node at the front of the
Binder or voiD0 (NULL void pointer) if there are no
nodes. The front of the Binder is considered to be the
top of the stack or the front of the deque-queue.
voiD topAsg(voiD D) { return atGetAsg(0,D); }
Same of top() except the contents of the top node is
copied via the Dassign() virtual function to the location
pointed to by D. If successful D is returned. The
BDR_DASSIGN flag must be set to enable this primitive.
voiD insQ(voiD D) { return atIns(nodes,D); }
Inserts the D pointer at the rear of the queue returning
this pointer if successful or voiD0, the NULL void
pointer, otherwise.
Binder& operator<<(voiD D) { atIns(nodes,D); return *this; }
This overloaded operator provides a convenient notation
for inserting into the queue.
voiD insQNew(const voiD D) { return atInsNew(nodes,D); }
Same as insQ() except what D points to is cloned via the
Dnew() protected virtual function and the pointer to the
clone is queued instead of D. If successful the pointer
to the clone is returned. The BDR_DNEW flag must be set
to enable this primitive.
voiD unQ() { return atRmv(nodes-1); }
Pops the rear of the queue returning its pointer.
Class: Binder binder.hpp
-----------------------------------------------------------------
int unQDel() { return atDel(nodes-1); }
Same as unQ() except the popped node is deleted via the
Ddelete() virtual function. Zero is returned to indicate
failure. The BDR_DDELETE flag must be set to enable this
primitive.
voiD unQDelAsg(voiD D) { return atDelAsg(nodes-1,D); }
Same as unQDel() except before deleting the popped node
its contents are copied via the Dassign() virtual
function to the location pointed to by D. If successful
D is returned. Both the BDR_DASSIGN and BDR_DDELETE
flags have to be set to enable this primitive.
voiD rear() { return atGet(nodes-1); }
Returns a pointer to the rear node of the queue.
voiD rearAsg(voiD D) { return atGetAsg(nodes-1,D); }
Same as rear() except the contents of the rear node is
copied via the Dassign() virtual function to the location
pointed to by D. If successful D is returned otherwise
NULL is returned. The BDR_DASSIGN flag must be set to
enable this primitive.
Class: Binder binder.hpp
-----------------------------------------------------------------
List primitives, public:
unsigned CurNode();
Returns the index to the list's current node if there is
one or BDR_NOTFOUND if the current node is undefined.
When a Binder is first constructed, its current node is
undefined.
int setCurNode(unsigned n = BDR_MAXNODES);
Resets the list's internal current node index to n if
within range, i.e. 0 to nodes-1, or to undefined if not.
BDR_MAXNODES is never in range so the current node is
left as undefined if setCurNode() is called without a
parameter. Returns false only if current node is left
undefined.
voiD ins(voiD D);
Inserts D into the Binder after the list's current node
making the inserted pointer, in the newly created cell,
the new current node. Returns D if successful, otherwise
it returns voiD0, the NULL void pointer. If at the
outset, the current node is undefined then the node
pointer is inserted at the rear of the list and made the
new current node. Remember, a list may or may not have
a current node defined.
voiD insNew(const voiD D);
Same as ins() except what D points to is cloned via the
Dnew() virtual function and the pointer to the clone is
inserted. If successful the pointer to the clone is
returned otherwise voiD0, the NULL void pointer, is
returned. The BDR_DNEW flag must be set to enable this
primitive.
Class: Binder binder.hpp
-----------------------------------------------------------------
voiD rmv();
Destroys the current node cell returning its pointer.
The current node setting is decremented making the node
ahead of the node just extracted current. If the first
node of the list is extracted the current node index is
left unchanged. If the first node is also the last node
the current node becomes undefined. If the operation
fails for any reason, e.g. current node undefined at the
outset or no nodes in the list, voiD0, the NULL void
pointer, is returned.
int del();
Same as rmv() except the removed node is deleted via the
Ddelete() protected virtual function. Zero is returned
to indicate failure. The BDR_DDELETE flag must be set to
enable this primitive.
voiD delAsg(voiD D);
Same as del() except the contents of the node is copied
via the Dassign() protected virtual function to the
location pointed to by D before the node is itself is
deleted. If successful D is returned. Both the
BDR_DASSIGN and BDR_DDELETE flags have to be set to
enable this primitive.
voiD put(voiD D) { return atPut(curNode,D); }
Overwrites the current node's pointer with D, the new
node pointer, returning D. If D is NULL or the current
node index is undefined then nothing is overwritten and
voiD0 (NULL void pointer) is returned. Before the old
pointer is overwritten, it is deleted via Ddelete if the
BDR_DDELETE flag is set.
Class: Binder binder.hpp
-----------------------------------------------------------------
voiD putNew(const voiD D) { return atPutNew(curNode,D); }
The BDR_DNEW flag must be set to enable this primitive.
Performs the same function as put() except the data
pointed to by D is cloned via Dnew() and the pointer to
the clone is put. If successful the clone's pointer is
returned, otherwise NULL is returned.
voiD putAsg(const voiD D) { return atPutAsg(curNode,D); }
The BDR_DASSIGN flag must be set to enable this
primitive. Copies the data pointed to by the D parameter
to the current node. The actual copying is performed by
the Dassign() protected virtual function. If successful,
the pointer to the bound node is returned.
voiD get() { return atGet(curNode); }
Returns the pointer to the list's current node or voiD0,
the NULL void pointer, if no node is current or there are
no nodes.
operator TYPE *() { return atGet(curNode); }
This implicit type cast operator provides a convenient
notation for calling get(). It is defined in template
and form instances where TYPE is the type of data being
bound in the Binder. MBinders define TYPE as Mutual.
Typeless Binders do not define this operator.
voiD getAsg(voiD D) { return atGetAsg(curNode,D); }
Same as get() except the current node is copied via the
Dassign() protected virtual function to the location
pointed to by D. If successful D is returned. The
BDR_DASSIGN flag must be set to enable this primitive.
Class: Binder binder.hpp
-----------------------------------------------------------------
voiD next();
Advances the list's current node index returning the
pointer to the next node. Upon reaching the end of the
list next() returns voiD0, the NULL void pointer. A
subsequent call to next() causes the current node index
to wrap around to zero, the index of the first node in
the list, and the process of walking the list can begin
anew.
voiD operator++() { return next(); }
This overloaded prefix operator provides a convenient
notation for calling next().
voiD nextAsg(voiD D);
Same as next() except the contents of the new current
node is copied via the Dassign() protected virtual
function to the location pointed to by D. If successful
D is returned. The BDR_DASSIGN flag must be set to
enable this primitive.
voiD prev();
Decrements the list's current node index returning the
pointer to the new current node. After reaching the
front of the list, the next call to prev() returns voiD0,
the NULL void pointer. A subsequent call to prev()
causes the current node index to wrap around positioning
itself on the last node of the list and the process of
walking backwards across the list can begin anew.
voiD operator--() { return prev(); }
This overloaded prefix operator provides a convenient
notation for calling prev().
Class: Binder binder.hpp
-----------------------------------------------------------------
voiD prevAsg(voiD D);
Same as prev() except the contents of the current node is
copied via the Dassign() protected virtual function to
the location pointed to by D. If successful D is
returned. The BDR_DASSIGN flag must be set to enable
this primitive.
voiD firstThat(BDRdetecT B, voiD M = voiD0);
This Binder iterator applies the function pointed to by
B to each node in sequence until the function returns non
zero. It leaves the current node set to the node found
returning its pointer. If the function fails to detect
a node the current node becomes undefined and NULL is
returned. The M parameter is user definable being simply
passed on the to the B function along with the node
pointer. The function pointer type is defined as:
typedef int (*BDRdetecT)(voiD D, voiD M);
voiD lastThat(BDRdetecT B, voiD M = voiD0);
This Binder iterator is similar to firstThat() except it
scans across the Binder from the rear to the front.
Class: Binder binder.hpp
-----------------------------------------------------------------
Sort and Search primitives, public:
unsigned Sorted() { return (flags & BDR_SORTED); }
Returns true if the Binder is still sorted since its last
sort. For example, if a Binder was sorted and then nodes
popped from it, it would still be sorted so Sorted()
would return true. But if it had been pushed since the
last sort, the sorted order couldn't still be guaranteed
so Sorted() would return false. It is possible to
disturb a Binder's sorted order without the Binder being
able to detect it, e.g. using the pointer returned from
atGet() to modify a key field of a node while it is still
bound within the Binder. In such cases you should call
unSort() to let the Binder know that it can no longer
guarantee a sorted order.
void unSort() { flags &= ~BDR_SORTED; }
See explanation of Sorted().
void setComP(BDRcomP comP = BDRcomP0)
{ this->comP = comP; flags &= ~BDR_SORTED; }
Sets the Binder's compare function which is used by
sort(), insSort(), insUnique(), findFirst(), etc. The
compare function must return zero to indicate a match and
a value greater than zero if its first parameter is
greater than its second. The compare function pointer
type is defined as:
typedef int (* BDRcomP)
(const voiD D1, const voiD D2);
#define BDRcomP0 (( BDRcomP)0)
BDRcomP ComP() { return comP; }
Returns the compare function pointer stored in the
Binder's header.
Class: Binder binder.hpp
-----------------------------------------------------------------
int sort(BDRcomP comP = BDRcomP0);
Uses a binary sort to sort the list in conjunction with
the user supplied compare function. If the compare
function pointer is NULL then the previously set compare
function pointer is used. If the previous function
pointer is NULL also then the list obviously can't be
sorted and false is returned. The compare function
pointer type is defined as:
typedef int (* BDRcomP)
(const voiD D1, const voiD D2);
#define BDRcomP0 (( BDRcomP)0)
voiD insSort(voiD D);
Uses the previously set compare function pointer to
insert the D pointer into the Binder in a sorted order
according to the key contained in the node pointed to by
D. What is or isn't a key is defined by the compare
function. If the list isn't sorted, it is first sorted
and then the insertion sort of D takes place. In either
case the newly insert node becomes the list's new current
node. If either the compare function pointer or D is
NULL then the primitive fails and voiD0, the NULL void
pointer is returned and the current node is left
undefined. Otherwise D is returned.
voiD insSortNew(const voiD D);
Same as insSort() except what D points to is cloned via
the Dnew() protected virtual function and the clone is
inserted in sorted order. If successful a pointer to the
clone is returned. The BDR_DNEW flag must be set to
enable this primitive.
Class: Binder binder.hpp
-----------------------------------------------------------------
voiD insUnique(voiD D);
Calls findFirst() to see if there is a match for D. If
not D is inserted into the Binder via insSort().
voiD insUniqueNew(const voiD D);
Same as insUnique() except D is cloned via Dnew() and the
cloned inserted in sorted order. If successful the
pointer to the clone is returned otherwise NULL is
returned. The BDR_DNEW flag must be set to enable this
primitive.
voiD findFirst(const voiD K);
Uses the previously set compare function to find a node
matching K. Since the compare function is user definable
any field in the node can be used a key. The compare
function is called with the node being tested as its
first parameter and K as its second. Since this is the
same compare function that is used by insSort() and
Sort() it is best if the compare function expects K to be
the same type of data as the node being compared. If
someone maintaining your code years from now tries to
call insSort() or sort() with your compare function
written for a K of some other data type, he/she may have
trouble finding out what went wrong with a sort() call
using the non symmetrical compare function!
If the list is sorted then findFirst() uses a binary
search to find the first match, otherwise it uses a
linear search. If all goes well the pointer to the
matching node is returned otherwise voiD0, the NULL void
pointer, is returned. The current node index is set to
that of the matching node or to undefined if no match is
found.
Class: Binder binder.hpp
-----------------------------------------------------------------
voiD findNext(const voiD K);
If the list is sorted the next node past the list's
current node is tested for a match with K via the
previously set user defined compare function. If it is
a match the current node index is advanced and a pointer
to that node is returned. If it isn't, findNext()
returns voiD0, the NULL void pointer, after setting the
current node index to undefined.
If the list isn't sorted at the outset then the list is
scanned starting with the first node past the current
node and continues looking until the end of the list is
reached. If a match is found that node becomes the new
current node and its pointer returned, otherwise the
current node is set to undefined and voiD0, the NULL void
pointer, is returned.
While it is possible to call findNext() after calling
findLast() and findPrev() several times and come up with
a match in a sorted list, be aware that for sorted lists
the current node must be immediately ahead or within the
contingent of matching nodes for findNext() to find them
as expected! In other words for sorted lists be sure to
call findFirst() first!
voiD findLast(const voiD K);
Where as findFirst() searches for its first match closest
to the front of the list, findLast() searches for its
first match closest to the rear of the list. Likewise,
if the list is sorted a binary search is made and if not
a linear search is made. The current node index is set
to the matching node and the node's pointer returned. If
there are no matching nodes then the current node index
is set to undefined and voiD0, the NULL void pointer, is
returned.
Class: Binder binder.hpp
-----------------------------------------------------------------
voiD findPrev(const voiD K);
If the list is sorted the previous node in the list is
tested for a match with K via the user defined compare
function previously set. If it is a match the current
node index is decremented and a pointer to that node is
returned. If it isn't, findNext() returns voiD0, the
NULL void pointer, after setting the current node index
to undefined.
If the list isn't sorted then the list is walked
backwards from the current node while looking for a match
or until dropping off the front of the list. If a match
is found that node becomes the new current node and its
pointer returned otherwise the current node is set to
undefined and voiD0, the NULL void pointer, is returned.
unsigned findAll(const voiD K);
Using the previously user defined compare function, each
node is checked against K and the matches tallied. The
current node is left undefined and the tally returned.
Class: MBinder Mutual ID: ID_MBinder mutual.hpp
-----------------------------------------------------------------
Being derived publicly from Mutual and privately from Binder, the
MBinder adds Mutual's behavior to a Binder. All of the Binder's
public member functions are in essence promoted to public scope in
the MBinder but with strong type checking wrappers like a Binder
form instance.
Functions, protected:
MBinder (initVFTs) : Binder(initVFTsOnly),
Mutual(initVFTsEtc) {}
The "default" constructor definition is straight forward.
See Binder:: Binder(initVFTs) and Mutual::
Mutual(initMutual) for details.
virtual voiD Dassign(voiD D, const voiD S);
Overrides Binder::Dassign() to call the
Mutual::operator=() virtual function.
virtual voiD Dnew(const voiD D);
Overrides Binder::Dnew() to call the Mutual::clone()
virtual function.
virtual void Ddelete(voiD D);
Overrides Binder::Ddelete() to check D, a Mutual
instance, for a refCount of zero before deleting.
virtual int Dattach(voiD D)
{ return ((MutuaL)D)->link(this); }
Overrides Binder::Dattach() which did nothing. As you
can see from its inline definition it increments the
Mutual instance's refCount.
Class: MBinder Mutual ID: ID_MBinder mutual.hpp
-----------------------------------------------------------------
virtual void Ddetach(voiD D)
{ ((MutuaL)D)->unlink(this); }
Overrides Binder::Ddetach() which did nothing. As you
can see from its inline definition it decrements the
Mutual instance's refCount. Suppose a Mutual node is
being deleted from an MBinder stack, i.e. popDel().
Since this is an MBinder, Ddetach() decrements the nodes
refCount and then the node is passed to Ddelete for
processing. If the refCount is zero it's delete. If not
it's simply discarded because it is still owned by some
other object.
virtual void Dstore(ostream& os, voiD D);
Overrides Binder::Dstore() to call the Mutual::fput()
virtual function.
virtual voiD Dload(istream& is);
Overrides Binder::Dload() to call the stream registry
fget() which extracts the instance's class id and
refCount from the stream. This id is used to look up and
call the associated static fget() of the matching class
derived from Mutual.
static MBindeR load(istream& is, MBindeR thiS);
This load is for use with Binder:: vload() and the file
name load constructor.
virtual void fput(ostream& os) { store(os); }
Overrides Mutual::fput() to call MBinder:: Binder::
store(). Mutual::fput() is invoked by the stream
registry's fput() after it inserts id and multilink
information into the stream. Then Binder::store() is
called to store the Binder portion. The stream registry
fput() is invoked by operator<<(ostream&, Mutual&).
Class: MBinder Mutual ID: ID_MBinder mutual.hpp
-----------------------------------------------------------------
static MutuaL fget(istream& is, MutuaL InstancE)
{ return (MutuaL)load(is,(MBindeR)InstancE); }
This fget() gets called from the stream registry's fget()
to load an MBinder whenever it encounters the ID_MBinder
id in a stream.
public:
MBinder (unsigned flags = BDR_DDELETE | BDR_DSTORE,
unsigned maxNodes = BDR_MAXNODES,
unsigned limit = BDR_LIMIT,
unsigned delta = BDR_DELTA) :
Binder(flags,maxNodes,limit,delta),
Mutual(initVFTsEtc) {}
This constructor reflects its Binder heritage except the
default flags have been changed to allow deletion and
storage. The Mutual base class simply zeros out
everything. The Mutual id is set in the overriding
definition of ID().
MBinder (MutuaL argv[],
unsigned argc = 0,
unsigned flags = BDR_BIND_ONLY)
: Binder((voiDV)argv,argc,flags),
Mutual(initVFTsEtc) {}
Again the constructor reflects its Binder heritage. The
default flags remain the same as they did for the Binder
this time.
Class: MBinder Mutual ID: ID_MBinder mutual.hpp
-----------------------------------------------------------------
MBinder (const char *filename)
: Binder(initVFTsOnly), Mutual(initVFTsEtc)
{ (void) vload(filename,(BDRsloaD)
MBinder::load,this); }
Here you can see how the vload() is passed the address of
the static MBinder::load() function effectively creating
a virtual constructor. This function bypasses the stream
registry, loading a Binder instead of an MBinder. The
nodes are still loaded as Mutual nodes owning to the
overriding of Dload(). It is just that the MBinder's id
and refCount are not expected ahead of the Binder. Use
this load constructor if, and only if, you used save() to
store it on the stream. This function is simply provided
for convenience. Without it you would have to use the
stream extraction operator on an opened file handle to
extract a Mutual object and then type cast it to an
MBinder (see examp601.cpp).
int save(const char *filename)
{ return ((!RefCount())? Binder::save(filename) : 0); }
Like the load constructor, save() bypasses the stream
registry, saving a Binder instead of an MBinder. Its
nodes are saved as Mutual nodes owing to the overriding
of Dstore(), however. It is just that no MBinder id or
refCount is inserted before the Binder on the stream by
the stream registry. If you use save(), you must use the
load constructor to reload! Notice that save() won't
work if there are any links to the MBinder.
virtual ~MBinder() { Binder::destruct(); }
See Binder:: destruct().
Class: MBinder Mutual ID: ID_MBinder mutual.hpp
-----------------------------------------------------------------
inline ostream& operator<<(ostream& os, MBinder& b)
{
if (b.Flags(BDR_DSTORE))
os << *(MutuaL)&b;
return os;
}
This stream insertion operator for MBinders insures that
the BDR_DSTORE flag is raised before invoking the stream
registry fput() via the overloaded Mutual stream
insertion operator. Unlike the save() function, the id
and refCount are inserted ahead of the Binder and its
nodes by the stream registry. If you save an MBinder
with this stream insertion operator you must reload it
with the stream extraction operator for Mutual pointers
(see examp602.cpp).
static void RegisterClass()
{ Mutual::RegisterClass(ID_MBinder, MBinder::fget); }
Registers the MBinder loader function with the stream
registry via the Mutual::RegisterClass() function. The
stream registry calls this loader to reload an MBinder
from stream.
virtual unsigned ID() { return ID_MBinder; }
Overrides Mutual:: ID() returning the id of an MBinder.
virtual unsigned restream();
Overrides Mutual::restream() to additionally call each
node's restream() function.
Class: Mutual mutual.hpp
-----------------------------------------------------------------
Inherited by: MBinder
Descendants: must be derived from a public Mutual hierarchy!
Friends: StreamRegistry
Mutual is a conceptually abstract class that provides for mutual
ownership and streamable behavior in its descendants. Mutual
objects are able to record multiple reference counts which can be
used to avoid accidental deletion. A Mutual object's most
important job, however, is to be able to store itself on a stream
and later be reloaded. Even if a Mutual object is multiply
referenced, any attempt to store the objects referencing it will
result in only one copy of the Mutual object being stored. When
reloaded from a stream, only one copy of the Mutual instance will
be reconstructed and the multiple links will be automatically
reestablished.
private:
voiD parenT;
The first object to "link()" to the instance with a non
NULL parent pointer while the instance's parenT is NULL
will automatically establish a back link to that object.
When the back link parent "unlinks()", parenT is reset to
NULL (see ParenT()). This back link makes it easy for
MBinders containing nodes that are MBinders to backwards
navigate across the orthogonal lists. Also, it can be
quite handy for the Mutual nodes of a MBinder to be able
to access local list data stored in an instance derived
from MBinder or perhaps even member functions.
unsigned refCount;
Records the number of "link()s" made to instance.
RefCount is incremented by link() and decremented by
unlink().
Class: Mutual mutual.hpp
-----------------------------------------------------------------
unsigned streamCount;
The number of times the instance has been written to the
stream during the current streaming operation. Note that
only the first time is the instance written and any
additional attempts to write only writes a multiple
reference mark. StreamCount must be less than refCount
in order for an instance to be streamed. This is why a
MBinder must be "link()ed" before it can be streamed (via
the stream insertion operator and stream registry).
long streamPos;
Once an instance is written to a stream its position on
the stream is recorded internally to facilitate the
writing of the multiple reference marks on the stream.
The following chart shows what happens during a streaming
operation. Notice that if an instance is referenced only once
then it will only be streamed once. The parent pointer is not
streamed so in order for this link to persist you must insure
that the dominate link, the first link that reaches the
instance when storing, is the parent link so that it will be
automatically reconstructed when "re-link()ing" the loaded
instance. StreamCount isn't streamed since a reloaded
instance's streamCount is always zero. The user's data
resides in descendants of Mutual. Only on the first encounter
with a instance during a streaming operation is it necessary
to store the user's data. The next time only a multiple
reference marker is written to the stream.
Stream contents:
1st & Only 1st of Many MultiRef
ID() x x 0
refCount 1 > 1
streamCount
streamPos x
user's data x x
Class: Mutual mutual.hpp
-----------------------------------------------------------------
When the stream is subsequently reloaded, the first time an
instance is encountered, the stream registry will load its id.
If its id is non zero then refCount is read and the id is used
to look up the Mutual class' descendant's static fget()
function, which is called to load the descendant's data.
RegisterClass() registered this fget() function with the
registry. The descendant's fget() function will call its
default constructor which in turn calls Mutual(initMutual)
which initializes parenT to NULL and refCount, streamCount,
and streamPos to zero. If the refCount read from the stream
is greater than one then the pointer to the loaded Mutual
instance returned from the descendant's fget() function is
also saved in a holding pen (internal to the registry) along
with the reloaded refCount and the instance's position on the
stream. The pointer to the instance is returned by the
overloaded stream extraction operator, i.e.
operator>>(istream&,MutuaL&) as usual.
If the id is zero then the instance has already been loaded
and its pointer is being held in the holding pen of the stream
registry. In this case streamPos is read from the stream's
multiple reference mark and used to uniquely identify the
instance being held in the holding pen. Instead of calling
the descendant's fget() function, the pointer retrieved from
the holding pen is returned by the stream extraction operator.
When the last multiple reference link to a instance held in
the holding pen is reconstructed, the holding pen
automatically releases the instance. The holding pen is
simply a Binder within the stream registry.
Class: Mutual mutual.hpp
-----------------------------------------------------------------
protected:
Mutual (initMutual);
The constructor zeros parenT, refCount, streamCount and
streamPos. The parameter is a dummy to set a pattern for
unique descendant default constructors. The purpose of
this default constructor chaining in descendants is to
provide a way for the fget() functions to separate
construction from initialization. This allows the top
tier fget() to initialize virtual function tables, etc.
The reason it doesn't initialize any descendant data is
because in a hierarchy of descendants, the fputs()'s and
fget()'s may choose to store/load an ancestor's data
before/after its own. If this constructor were to
initialize data in a descendant this option wouldn't be
available! Nor could each tier be responsible for
insertion/extraction of its respective data to/from a
stream. Please note that once the virtual function table
is initialized the virtual functions become operative and
ID() returns its correct value for any descendants!
static void serror(const char *msg, unsigned id);
The stream error function is static so that it can be
called by the static fget() function. Being static an id
must be supplied as a parameter since there is no
instance implied from which id can be retrieved. If
streamDebug is non zero the message is streamed to cerr.
virtual void error(const char *msg);
Stream error function typically called by fput(), reports
the message on cerr if streamDebug is non zero.
virtual void warn(const char *msg);
Stream warning function typically called by fput(),
reports the message on cerr if streamDebug is non zero.
Class: Mutual mutual.hpp
-----------------------------------------------------------------
virtual void fput(ostream& os);
Called by the stream registry to store the data in
descendants of Mutual.
static MutuaL fget(istream& is, MutuaL InstancE)
{ return InstancE; }
Mutual::fget() does nothing. It is used to set a pattern
for a chain of fget()'s in derived classes. A derived
class' fget() is called by the stream registry to load an
instance of that descendant if its InstancE parameter is
NULL. It must then dynamically construct a default
instance of itself using the chain of default
constructors discussed earlier. It then proceeds to
extract its class' tier data from stream and then calls
the base class fget()'s with non NULL InstancE parameters
to load their respective tier data. So you see an fget()
function tests its InstancE parameter to see whether it
was called from the stream registry as the top tier or
from a tier from above in which case it doesn't call the
chain of constructors.
Class: Mutual mutual.hpp
-----------------------------------------------------------------
public:
static int refDebug;
Turns on refCount inc/dec and over/under - flow messages
to cerr (off by default).
static int streamDebug;
Turns on stream debugging messages to cerr (off by
default).
static char memberTermChar;
Defines the character used to delimit member fields of an
instance on the stream.
Though not members of Mutual proper, the following stream
manipulators are used to insert/extract the memberTermChar.
ostream& Mendm(ostream& os);
istream& Mnextm(istream& is);
Mutual (Mutual&) { Mutual(initVFTsEtc); }
Like the default constructor it calls, this copy
initializer constructor is used for building chains of
copy initializer constructors in its descendants. The
clone() function invokes the copy initializer using the
new operator. The clone function is typically called by
MBinder:: Dnew() which in turn is called by MBinder
primitives having "New" suffixes, e.g. atInsNew().
static void RegisterClass(unsigned id,
MutuaL (*fgeT)(istream&, MutuaL));
Called by the descendant class static function of the
same name before attempting to stream instances of that
class. There is no need to register any intermediate
classes, only those with instances that are streamed.
Class: Mutual mutual.hpp
-----------------------------------------------------------------
virtual int operator=(Mutual&) { return 0; }
This function is typically called by MBinder:: Dassign()
which in turn is called by MBinder primitives having
"Asg" suffixes, e.g. nextAsg(). Returning zero indicates
failure effectively turning this function "off." If
Dassign() receives a failure indication it passes it on
to its calling function, e.g. nextAsg(), which then
returns failure.
virtual MutuaL clone() { return MutuaL0; }
This function is overridden in all derived classes to
call their copy initializer constructors. Thus clone()
behaves like a virtual copy initializer for the Mutual
polymorphic cluster of classes. The clone function is
typically called by MBinder:: Dnew() which in turn is
called by MBinder primitives having "New" suffixes, e.g.
atInsNew(), thus allowing heterogeneous nodes to be
cloned on the fly in the same MBinder!
voiD ParenT() { return parenT; }
The parenT pointer is automatically set to the first
linking parent that identifies itself and reset to NULL
if that parent identifies itself when unlinking, see
link() and unlink().
virtual unsigned ID() { return ID_Mutual; }
Overridden in all descendant classes to return its unique
id.
Class: Mutual mutual.hpp
-----------------------------------------------------------------
virtual unsigned restream();
Resets streamCount and streamPos to zero so that another
store on stream operation can be made. StreamCount and
streamPos are used by the automatic multiple storage
mechanism. StreamCount doesn't allow the instance to be
streamed out more times than it has been notified that it
is referenced, i.e. refCount via link(). StreamCount
also tells the stream registry if this is the first time
the instance is being streamed. If so the descendant's
data is streamed out via Mutual::fput() after the stream
registry outputs the id and refCount. If not the first
time, the multiple reference mark, ID_MRef, is streamed
out instead of the id and streamPos is stream out
recording the position on the stream of the actual
instance. After streaming a network of Mutual instances,
streamCount should equal refCount for each instance owing
to the fact that refCount should be the number of ways an
instance can be reached over the network, i.e. the number
of link()s. Restream() reports the difference between
refCount and streamCount before resetting streamCount and
streamPos whenever streamPos is non zero. If streamDebug
is non zero the discrepancy is reported on cerr. If
streamPos is zero, restream() returns zero since no
streaming has taken place since the last restream() or
constructor call. See Restream() for the StreamRegistry.
void unlink(voiD P = voiD0);
Decrements refCount if non zero, returning its value. If
refDebug is turned on, the refCount value, before
decrementing, and any underflow is reported on cerr. If
P is equal to parenT then parenT is reset to voiD0, the
NULL void pointer. Unlink() is called by MBinder::
Ddetach() which in turn is called by MBinder unbinding
primitives, e.g. atDel(), atPut(), and atXchg(), for
automatic unlinking of Mutual nodes.
Class: Mutual mutual.hpp
-----------------------------------------------------------------
int link(voiD P = voiD0);
Increments refCount if less than UINT_MAX to indicate
additional references in memory to the instance. If
refDebug is non zero then a message is streamed to cerr
reporting the new refCount value and the address of the
instance so that multiple linking can be traced.
Overflow errors are likewise reported on cerr. If P is
not NULL and parenT is, P is assigned to parenT. Link()
is called by MBinder:: Dattach() which in turn is called
by MBinder binding primitives, e.g. atIns(), atGet(), and
atXchg(), for automatic linking of Mutual nodes.
unsigned RefCount() { return refCount; }
Called by MBinder:: Ddelete() to see if a Mutual node can
be safely deleted. Only when zero is returned is it okay
to delete a Mutual node. See link() and unlink().
long StreamPos() { return streamPos; }
Provided for building Mutual object file indexes. After
streaming out and before restream() is called, call
StreamPos() to determine the position of the Mutual
object in the file.
virtual ~Mutual() {}
Provided so that descendants can be virtually destroyed.
Though not members of Mutual proper, the following stream
operators are used to insert and extract Mutual instances and
pointers respectively (see StreamRegistry).
inline ostream& operator<<(ostream& os, Mutual& m)
{ StreamReg.fput(os,m); return os; }
inline istream& operator>>(istream& is, MutuaL& M)
{ StreamReg.fget(is,M); return is; }
Class: StreamRegistry mutual.hpp
-----------------------------------------------------------------
The StreamRegistry provides the mechanism for allowing
instances of classes derived from Mutual to be written out to
a stream and later read back into memory from the stream.
There is only one instance of StreamRegistry. Each class
derived from Mutual defines its own RegisterClass() static
member function which calls the this instance for you. The
following macros make the StreamRegistry instance transparent
to the programmer.
#define Restream() StreamReg.restream()
The Restream() macro expands into a call to the
restream() member function without the need for a
instance name. When Mutual instances are reloaded from
a stream back into memory, instances with multiple
references are loaded only once while pointers to these
instances are held in a holding pen within the
StreamRegistry so that the multiple references can be
reconstructed as they are encountered. Once the stream
is fully loaded all instances should have already been
automatically released from the holding pen. Restream()
releases any stragglers, reporting the relinking
discrepancies on cerr if StreamRegistry:: debug is set.
Restream() reports the number of stragglers. You should
call Restream() after every reloading operation to insure
that holding pen is clear. StreamRegistry:: debug is
reset by default.
#define RegisterFunction(id,fnC) \
StreamReg.registerFunction(id,(GenericFnC)fnC)
Use this macro to you register the function pointers you
want to stream in much the same way you registered
classes. Please note that some CPU architectures allow
C++ compilers to generator different size function
pointers, e.g. class member function pointers verses
standard function pointers. This macro will correctly
register any function pointer that is the same size as
the function pointer specified by the following typedef.
typedef void (*GenericFnC)();
Class: StreamRegistry mutual.hpp
-----------------------------------------------------------------
#define fnC2ID(fnC) StreamReg.fnCID((GenericFnC)fnC)
Once registered, use this macro to convert a function
pointer to its id.
#define ID2fnC(fnCType,id) (fnCType) StreamReg.fnCLU(id)
Once registered, use this macro to convert an id to its
function pointer. The "fnCType" is the type of your
original function pointer.
The following example tests the function pointer macros.
// examp702.cpp - link with mutual.obj and binder.obj
#include "mutual.hpp"
#define ID_test 5
char * test(char * s1, char *s2)
{ cout << s1; return s2; }
main() {
RegisterFunction(ID_test,test);
cout <<(*ID2fnC(char *(*)(char *, char *),
fnC2ID(test)))
("\nGoodbye persistence headaches!\n",
"Hello streamable functions!\n");
return 0;
}
First the function pointer is registered. Then the function
pointer is converted to an id which is converted back to a
function pointer which is invoked with the two string
parameters returning a string to stream to cout. You can use
the fnC2ID() macro to save a function pointer's id inside your
overriding Mutual::fput() function. Inside fget(), the
extracted id is converted back to a function pointer with the
ID2fnC() macro.
Class: StreamRegistry mutual.hpp
-----------------------------------------------------------------
#define ForgetRegistrations() \
StreamReg.forgetRegistrations()
When you no longer need the class and function pointer
registrations, use this macro to clear the stream
registry.
Though not members of the StreamRegistry proper, the following
stream operators are used to insert and extract Mutual
instances and pointers respectively.
inline ostream& operator<<(ostream& os, Mutual& m)
{ StreamReg.fput(os,m); return os; }
inline istream& operator>>(istream& is, MutuaL& M)
{ StreamReg.fget(is,M); return is; }
Index
~Binder() 58, 64, 66
~MBinder() 90
~Mutual() 100
allDel() 17, 57, 64, 71
allRmv() 17, 26, 70
assignment versus
initialization 47
assignment 12, 25, 47, 55
AT&T 9
atDel() 17, 71
atDel
Asg() 17, 71
atGet() 17, 72
atGetAsg() 17, 72
atIns() 17, 70
atInsNew() 17, 70
atPut() 17, 71
atPutAsg() 17, 72
atPutNew() 17, 72
atRmv() 17, 70
atXchg() 17, 73
back link 92, 93
bags 22
BASE 42
BDR_ALL_FLAGS 68
BDR_BIND_ONLY 57
BDR_DASSIGN 25, 57
BDR_DDELETE 25, 57
BDR_DELTA 64
BDR_DNEW 25, 57
BDR_DSTORE 57
BDR_LIMIT 64
BDR_MAXNODES 64
BDR_NOTFOUND 73
BDR_SORTED 57
BDRapplY 73
BDRcomP 82
BDRdetecT 81
BDRendm 35, 63
BDRnextm 35, 63
berror() 38, 60
Binder() 58, 64-65
BindeR0 62
chain 43, 96
CLASS 42
classroom 3
clone 25
clone() 47, 98
cloning 25, 44, 98
commerical class
designers 7
comP 57
ComP() 22, 82
compare function
registration 32
comPID() 57
comPLU() 57
comPv 57
construction versus
initialization 43
constructor address 43
conventional 7
cookbook 14
copy initializer 47
copyright 1, 3
CPTR 42
curNode 56
CurNode() 20, 77
current node index 20
Dassign() 14, 26, 28, 58, 87
Dattach() 59, 87
Ddelete() 14, 26, 29, 59, 87
Ddetach() 60, 88
debug 51
default constructor 43
del() 20, 78
delAsg() 20, 78
deleting 26, 29
delta 56
Delta() 17, 66
deque 19, 74
destruct() 58
Dload() 14, 33, 36, 61, 88
Dnew() 14, 26, 28, 59, 87
Dstore() 14, 33, 36, 60, 88
elastic-array 17, 70
error() 95
examp201 11
examp202 12
examp203 13
examp204 14
examp301 18
examp302 19
examp304 22
examp401 25
examp402 26
examp403 29
examp501 31
examp502 34
examp503 36
examp504 37
examp601 48
examp602 52
examp701 69
examp702 102
examples.cpp 9
extensible 43
fbinder 13-14
FBinder 13, 56
FBindeR 13, 56
fget() 45, 89
FIFO 19
findAll() 22, 86
findFirst() 22, 84
findLast() 22, 85
findNext() 22, 85
findPrev() 22, 86
first 56
firstThat() 20, 81
flags 57
Flags() 68
flat 7, 17
fmutual 41
fnC2ID() 101
FOLO 19
forEach() 17, 73
ForgetComPs() 63
ForgetRegistrations() 103
Form Cookbook 14
Form "Templates" 13
forms 13-14
fput() 44, 88, 96
FType 13, 56
function pointers 63, 101-102
GenericFnC 101
get() 20, 79
getAsg() 20, 79
granularity 7, 17
holding pen 51
Hybrid Container 17
hysteresis 7, 17
ID() 91, 93
ID_MBinder 89
ID_MRef 99
ID2fnC() 101
implict type cast 38, 79
index() 17, 73
initData() 44, 57
initialization versus
construction 43
initialization versus
assignment 47
initMutual 95
initVFTs 58
initVFTsEtc 97
initVFTsOnly 87
ins() 20, 77
insNew() 20, 77
insQ() 19, 75
insQNew() 19, 75
insSort() 22, 83
insSortNew() 22, 83
installation 9
insUnique() 22, 84
insUniqueNew() 22, 84
lastThat() 20, 81
libsrc.cpp 9
license agreement 3
LIFO 19
limit 56
Limit() 17, 66
link() 99, 100
linkS 56
list 20, 77
load() 62, 88
lowLimit 56
lowThreshold 56
makefile 9
manipulator 69
maxNodes 56
MaxNodes() 17, 67
MBinder() 87, 89, 90
memberTermChar 35, 63, 97
Mendm 97
Mnextm 97
multiple references 41, 53,
94, 99
Mutual() 95, 97
MutuaL0 98
mutually owned 41
next() 20, 80
nextAsg() 20, 80
nodes 56
Nodes() 17, 67
OEM 7
operator TYPE *() 38, 79
operator--() 20, 80
operator++() 20, 80
operator<<() 19, 69, 75, 91,
100, 103
operator=() 98
operator>>() 19, 74, 94, 100,
103
operator[]() 17, 72
pack() 17, 66
parameterize 41
parenT 92
ParenT() 98
permission 3
persistence 31
polymorphic cluster 41
pop() 19, 74
popDel() 19, 74
popDelAsg() 19, 74
portability 3
post card 4
prev() 20, 80
prevAsg() 20, 81
priority queue
push() 19, 74
pushNew() 19, 74
put() 20, 78
putAsg() 20, 79
putNew() 20, 79
queue 19, 74
rear() 19, 76
rearAsg() 19, 76
refCount 92, 94, 99
RefCount() 100
refDebug 51, 97
RegisterClass() 91, 97
RegisterComP() 63
RegisterFunction() 101
registration 4
resetFlags() 68
restream() 91, 99
Restream() 51, 101
rmv() 20, 78
royalty 3
save() 65, 90
sberror() 60
search 22, 82
self cleaning 53
sequential 20
serror() 95
setComP() 22, 82
setCurNode() 20, 77
setDelta() 17, 67
setFlags() 68
setLimit() 17, 66
setMaxNodes() 17, 67
sets 22
smart linker/librarians 9
sort 22, 82
sort() 22, 83
Sorted() 22, 82
stack 19, 74
store() 61
stream error logging 38, 95
streamCount 93-94, 99
streamDebug 38, 51, 63, 97
streamPos 93-94, 99
StreamPos() 100
StreamReg 101
StreamRegistry versus
Binder streaming 51-52,
90-91
StreamRegistry 101
subscripts 72
switches 51
symmetrical 84
tbinder 12
TBINDER() 12, 55
technical support 4
template 12
template binders 12
tier 43, 46, 96
top() 19, 75
topAsg() 19, 75
type checking 11
typeless Binders 11
unconventional 7, 17
unique 22, 82
unlink() 99
unQ() 19, 75
unQDel() 19, 76
unQDelAsg() 19, 76
unSort() 22, 82
vacancy() 17, 67
vacancyNonElastic() 17, 67
vector() 17, 65
virtual constructor 90
vload() 62, 65
voiD 64
voiD0 70
voiDV 65
warn() 95
warranty 4